summaryrefslogtreecommitdiffstats
path: root/luni/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'luni/src/main/java')
-rw-r--r--luni/src/main/java/android/system/ErrnoException.java82
-rw-r--r--luni/src/main/java/android/system/GaiException.java83
-rw-r--r--luni/src/main/java/android/system/Os.java539
-rw-r--r--luni/src/main/java/android/system/OsConstants.java (renamed from luni/src/main/java/libcore/io/OsConstants.java)92
-rw-r--r--luni/src/main/java/android/system/StructAddrinfo.java (renamed from luni/src/main/java/libcore/io/StructAddrinfo.java)41
-rw-r--r--luni/src/main/java/android/system/StructFlock.java (renamed from luni/src/main/java/libcore/io/StructFlock.java)30
-rw-r--r--luni/src/main/java/android/system/StructGroupReq.java (renamed from luni/src/main/java/libcore/io/StructGroupReq.java)23
-rw-r--r--luni/src/main/java/android/system/StructGroupSourceReq.java41
-rw-r--r--luni/src/main/java/android/system/StructLinger.java (renamed from luni/src/main/java/libcore/io/StructLinger.java)34
-rw-r--r--luni/src/main/java/android/system/StructPasswd.java48
-rw-r--r--luni/src/main/java/android/system/StructPollfd.java49
-rw-r--r--luni/src/main/java/android/system/StructStat.java98
-rw-r--r--luni/src/main/java/android/system/StructStatVfs.java (renamed from luni/src/main/java/libcore/io/StructStatVfs.java)15
-rw-r--r--luni/src/main/java/android/system/StructTimeval.java (renamed from luni/src/main/java/libcore/io/StructTimeval.java)54
-rw-r--r--luni/src/main/java/android/system/StructUcred.java (renamed from luni/src/main/java/libcore/io/StructUcred.java)10
-rw-r--r--luni/src/main/java/android/system/StructUtsname.java55
-rw-r--r--luni/src/main/java/android/util/MutableBoolean.java (renamed from luni/src/main/java/libcore/util/MutableBoolean.java)12
-rw-r--r--luni/src/main/java/android/util/MutableByte.java (renamed from luni/src/main/java/libcore/util/MutableByte.java)12
-rw-r--r--luni/src/main/java/android/util/MutableChar.java (renamed from luni/src/main/java/libcore/util/MutableChar.java)12
-rw-r--r--luni/src/main/java/android/util/MutableDouble.java (renamed from luni/src/main/java/libcore/util/MutableDouble.java)12
-rw-r--r--luni/src/main/java/android/util/MutableFloat.java (renamed from luni/src/main/java/libcore/util/MutableFloat.java)12
-rw-r--r--luni/src/main/java/android/util/MutableInt.java (renamed from luni/src/main/java/libcore/util/MutableInt.java)12
-rw-r--r--luni/src/main/java/android/util/MutableLong.java (renamed from luni/src/main/java/libcore/util/MutableLong.java)12
-rw-r--r--luni/src/main/java/android/util/MutableShort.java (renamed from luni/src/main/java/libcore/util/MutableShort.java)12
-rw-r--r--luni/src/main/java/java/io/BufferedInputStream.java11
-rw-r--r--luni/src/main/java/java/io/Console.java47
-rw-r--r--luni/src/main/java/java/io/File.java19
-rw-r--r--luni/src/main/java/java/io/FileDescriptor.java13
-rw-r--r--luni/src/main/java/java/io/FileInputStream.java12
-rw-r--r--luni/src/main/java/java/io/FileOutputStream.java9
-rw-r--r--luni/src/main/java/java/io/InputStream.java20
-rw-r--r--luni/src/main/java/java/io/InputStreamReader.java6
-rw-r--r--luni/src/main/java/java/io/ObjectInputStream.java4
-rw-r--r--luni/src/main/java/java/io/ObjectOutput.java2
-rw-r--r--luni/src/main/java/java/io/ObjectOutputStream.java48
-rw-r--r--luni/src/main/java/java/io/OutputStreamWriter.java4
-rw-r--r--luni/src/main/java/java/io/PipedInputStream.java5
-rw-r--r--luni/src/main/java/java/io/PipedOutputStream.java2
-rw-r--r--luni/src/main/java/java/io/PipedReader.java2
-rw-r--r--luni/src/main/java/java/io/PipedWriter.java5
-rw-r--r--luni/src/main/java/java/io/RandomAccessFile.java11
-rw-r--r--luni/src/main/java/java/lang/AbstractStringBuilder.java79
-rw-r--r--luni/src/main/java/java/lang/CaseMapper.java19
-rw-r--r--luni/src/main/java/java/lang/CharSequence.java11
-rw-r--r--luni/src/main/java/java/lang/Character.java38
-rw-r--r--luni/src/main/java/java/lang/Double.java8
-rw-r--r--luni/src/main/java/java/lang/Float.java8
-rw-r--r--luni/src/main/java/java/lang/Integer.java70
-rw-r--r--luni/src/main/java/java/lang/Long.java65
-rw-r--r--luni/src/main/java/java/lang/ProcessManager.java8
-rw-r--r--luni/src/main/java/java/lang/Runtime.java65
-rw-r--r--luni/src/main/java/java/lang/StringToReal.java37
-rw-r--r--luni/src/main/java/java/lang/System.java627
-rw-r--r--luni/src/main/java/java/lang/ref/FinalizerReference.java30
-rw-r--r--luni/src/main/java/java/lang/ref/Reference.java200
-rw-r--r--luni/src/main/java/java/lang/ref/ReferenceQueue.java36
-rw-r--r--luni/src/main/java/java/lang/reflect/Array.java12
-rw-r--r--luni/src/main/java/java/lang/reflect/Modifier.java2
-rw-r--r--luni/src/main/java/java/math/BigDecimal.java8
-rw-r--r--luni/src/main/java/java/math/Multiplication.java78
-rw-r--r--luni/src/main/java/java/net/AddressCache.java50
-rw-r--r--luni/src/main/java/java/net/DatagramSocket.java70
-rw-r--r--luni/src/main/java/java/net/DatagramSocketImpl.java36
-rw-r--r--luni/src/main/java/java/net/HttpCookie.java62
-rw-r--r--luni/src/main/java/java/net/HttpURLConnection.java12
-rw-r--r--luni/src/main/java/java/net/Inet4Address.java2
-rw-r--r--luni/src/main/java/java/net/Inet6Address.java2
-rw-r--r--luni/src/main/java/java/net/InetAddress.java77
-rw-r--r--luni/src/main/java/java/net/InetUnixAddress.java2
-rw-r--r--luni/src/main/java/java/net/JarURLConnection.java20
-rw-r--r--luni/src/main/java/java/net/MulticastSocket.java6
-rw-r--r--luni/src/main/java/java/net/NetworkInterface.java4
-rw-r--r--luni/src/main/java/java/net/PlainDatagramSocketImpl.java43
-rw-r--r--luni/src/main/java/java/net/PlainSocketImpl.java47
-rw-r--r--luni/src/main/java/java/net/ServerSocket.java43
-rw-r--r--luni/src/main/java/java/net/Socket.java74
-rw-r--r--luni/src/main/java/java/net/SocketImpl.java27
-rw-r--r--luni/src/main/java/java/net/SocketOptions.java81
-rw-r--r--luni/src/main/java/java/net/URI.java55
-rw-r--r--luni/src/main/java/java/net/URLConnection.java7
-rw-r--r--luni/src/main/java/java/net/URLStreamHandler.java8
-rw-r--r--luni/src/main/java/java/nio/Buffer.java23
-rw-r--r--luni/src/main/java/java/nio/ByteArrayBuffer.java2
-rw-r--r--luni/src/main/java/java/nio/ByteBuffer.java160
-rw-r--r--luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java3
-rw-r--r--luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java3
-rw-r--r--luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java3
-rw-r--r--luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java3
-rw-r--r--luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java3
-rw-r--r--luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java3
-rw-r--r--luni/src/main/java/java/nio/CharArrayBuffer.java2
-rw-r--r--luni/src/main/java/java/nio/CharBuffer.java85
-rw-r--r--luni/src/main/java/java/nio/CharSequenceAdapter.java2
-rw-r--r--luni/src/main/java/java/nio/DatagramChannelImpl.java248
-rw-r--r--luni/src/main/java/java/nio/DirectByteBuffer.java87
-rw-r--r--luni/src/main/java/java/nio/DoubleArrayBuffer.java2
-rw-r--r--luni/src/main/java/java/nio/DoubleBuffer.java44
-rw-r--r--luni/src/main/java/java/nio/FileChannelImpl.java29
-rw-r--r--luni/src/main/java/java/nio/FloatArrayBuffer.java2
-rw-r--r--luni/src/main/java/java/nio/FloatBuffer.java46
-rw-r--r--luni/src/main/java/java/nio/IntArrayBuffer.java2
-rw-r--r--luni/src/main/java/java/nio/IntBuffer.java44
-rw-r--r--luni/src/main/java/java/nio/IoVec.java2
-rw-r--r--luni/src/main/java/java/nio/LongArrayBuffer.java2
-rw-r--r--luni/src/main/java/java/nio/LongBuffer.java44
-rw-r--r--luni/src/main/java/java/nio/MappedByteBuffer.java12
-rw-r--r--luni/src/main/java/java/nio/MemoryBlock.java30
-rw-r--r--luni/src/main/java/java/nio/NIOAccess.java17
-rw-r--r--luni/src/main/java/java/nio/NioUtils.java8
-rw-r--r--luni/src/main/java/java/nio/PipeImpl.java4
-rw-r--r--luni/src/main/java/java/nio/SelectorImpl.java24
-rw-r--r--luni/src/main/java/java/nio/ServerSocketChannelImpl.java63
-rw-r--r--luni/src/main/java/java/nio/ShortArrayBuffer.java2
-rw-r--r--luni/src/main/java/java/nio/ShortBuffer.java44
-rw-r--r--luni/src/main/java/java/nio/SocketChannelImpl.java304
-rw-r--r--luni/src/main/java/java/nio/channels/DatagramChannel.java3
-rw-r--r--luni/src/main/java/java/nio/channels/FileChannel.java23
-rw-r--r--luni/src/main/java/java/nio/channels/FileLock.java2
-rw-r--r--luni/src/main/java/java/nio/channels/ServerSocketChannel.java3
-rw-r--r--luni/src/main/java/java/nio/channels/SocketChannel.java48
-rw-r--r--luni/src/main/java/java/nio/charset/CharsetDecoderICU.java18
-rw-r--r--luni/src/main/java/java/nio/charset/CharsetEncoderICU.java30
-rw-r--r--luni/src/main/java/java/security/AlgorithmParameterGenerator.java3
-rw-r--r--luni/src/main/java/java/security/AlgorithmParameters.java3
-rw-r--r--luni/src/main/java/java/security/GuardedObject.java2
-rw-r--r--luni/src/main/java/java/security/KeyFactory.java3
-rw-r--r--luni/src/main/java/java/security/KeyPairGenerator.java3
-rw-r--r--luni/src/main/java/java/security/KeyStore.java3
-rw-r--r--luni/src/main/java/java/security/MessageDigest.java27
-rw-r--r--luni/src/main/java/java/security/Provider.java246
-rw-r--r--luni/src/main/java/java/security/SecureRandom.java7
-rw-r--r--luni/src/main/java/java/security/Security.java46
-rw-r--r--luni/src/main/java/java/security/Signature.java217
-rw-r--r--luni/src/main/java/java/security/SignatureSpi.java5
-rw-r--r--luni/src/main/java/java/security/cert/CRLReason.java40
-rw-r--r--luni/src/main/java/java/security/cert/CertPathValidator.java3
-rw-r--r--luni/src/main/java/java/security/cert/CertStore.java4
-rw-r--r--luni/src/main/java/java/security/cert/Certificate.java10
-rw-r--r--luni/src/main/java/java/security/cert/CertificateFactory.java7
-rw-r--r--luni/src/main/java/java/security/cert/CertificateFactorySpi.java8
-rw-r--r--luni/src/main/java/java/security/cert/CertificateRevokedException.java155
-rw-r--r--luni/src/main/java/java/security/cert/Extension.java65
-rw-r--r--luni/src/main/java/java/security/cert/X509CRLEntry.java25
-rw-r--r--luni/src/main/java/java/security/interfaces/DSAParams.java4
-rw-r--r--luni/src/main/java/java/security/security.properties8
-rw-r--r--luni/src/main/java/java/security/spec/ECParameterSpec.java24
-rw-r--r--luni/src/main/java/java/sql/Date.java26
-rw-r--r--luni/src/main/java/java/sql/Timestamp.java70
-rw-r--r--luni/src/main/java/java/text/Bidi.java49
-rw-r--r--luni/src/main/java/java/text/BreakIterator.java35
-rw-r--r--luni/src/main/java/java/text/CollationElementIterator.java36
-rw-r--r--luni/src/main/java/java/text/DateFormat.java16
-rw-r--r--luni/src/main/java/java/text/DateFormatSymbols.java46
-rw-r--r--luni/src/main/java/java/text/DecimalFormat.java136
-rw-r--r--luni/src/main/java/java/text/DecimalFormatSymbols.java23
-rw-r--r--luni/src/main/java/java/text/FieldPosition.java244
-rw-r--r--luni/src/main/java/java/text/MessageFormat.java4
-rw-r--r--luni/src/main/java/java/text/NumberFormat.java45
-rw-r--r--luni/src/main/java/java/text/RuleBasedCollator.java304
-rw-r--r--luni/src/main/java/java/text/SimpleDateFormat.java106
-rw-r--r--luni/src/main/java/java/text/spi/BreakIteratorProvider.java90
-rw-r--r--luni/src/main/java/java/text/spi/CollatorProvider.java50
-rw-r--r--luni/src/main/java/java/text/spi/DateFormatProvider.java81
-rw-r--r--luni/src/main/java/java/text/spi/DateFormatSymbolsProvider.java50
-rw-r--r--luni/src/main/java/java/text/spi/DecimalFormatSymbolsProvider.java51
-rw-r--r--luni/src/main/java/java/text/spi/NumberFormatProvider.java93
-rw-r--r--luni/src/main/java/java/util/Arrays.java53
-rw-r--r--luni/src/main/java/java/util/Calendar.java16
-rw-r--r--luni/src/main/java/java/util/Collections.java2
-rw-r--r--luni/src/main/java/java/util/Currency.java14
-rw-r--r--luni/src/main/java/java/util/EnumMap.java4
-rw-r--r--luni/src/main/java/java/util/Formatter.java21
-rw-r--r--luni/src/main/java/java/util/GregorianCalendar.java60
-rw-r--r--luni/src/main/java/java/util/HashMap.java33
-rw-r--r--luni/src/main/java/java/util/IllformedLocaleException.java57
-rw-r--r--luni/src/main/java/java/util/LinkedHashMap.java5
-rw-r--r--luni/src/main/java/java/util/Locale.java1702
-rw-r--r--luni/src/main/java/java/util/Properties.java2
-rw-r--r--luni/src/main/java/java/util/Random.java17
-rw-r--r--luni/src/main/java/java/util/Scanner.java105
-rw-r--r--luni/src/main/java/java/util/TimeZone.java46
-rw-r--r--luni/src/main/java/java/util/Timer.java7
-rw-r--r--luni/src/main/java/java/util/TreeSet.java16
-rw-r--r--luni/src/main/java/java/util/UUID.java25
-rw-r--r--luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java3892
-rw-r--r--luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java2
-rw-r--r--luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java2
-rw-r--r--luni/src/main/java/java/util/concurrent/CountedCompleter.java113
-rw-r--r--luni/src/main/java/java/util/concurrent/ForkJoinPool.java1659
-rw-r--r--luni/src/main/java/java/util/concurrent/ForkJoinTask.java142
-rw-r--r--luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java18
-rw-r--r--luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java1
-rw-r--r--luni/src/main/java/java/util/concurrent/Phaser.java1
-rw-r--r--luni/src/main/java/java/util/concurrent/RecursiveAction.java1
-rw-r--r--luni/src/main/java/java/util/concurrent/RecursiveTask.java1
-rw-r--r--luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java2
-rw-r--r--luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java1
-rw-r--r--luni/src/main/java/java/util/concurrent/TransferQueue.java1
-rw-r--r--luni/src/main/java/java/util/concurrent/atomic/Fences.java1
-rw-r--r--luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java1
-rw-r--r--luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java1
-rw-r--r--luni/src/main/java/java/util/jar/Attributes.java9
-rw-r--r--luni/src/main/java/java/util/jar/JarEntry.java143
-rw-r--r--luni/src/main/java/java/util/jar/JarFile.java226
-rw-r--r--luni/src/main/java/java/util/jar/JarInputStream.java163
-rw-r--r--luni/src/main/java/java/util/jar/JarVerifier.java177
-rw-r--r--luni/src/main/java/java/util/jar/Manifest.java80
-rw-r--r--luni/src/main/java/java/util/jar/StrictJarFile.java232
-rw-r--r--luni/src/main/java/java/util/logging/SocketHandler.java7
-rw-r--r--luni/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java7
-rw-r--r--luni/src/main/java/java/util/prefs/FilePreferencesImpl.java25
-rw-r--r--luni/src/main/java/java/util/prefs/Preferences.java41
-rw-r--r--luni/src/main/java/java/util/regex/MatchResult.java58
-rw-r--r--luni/src/main/java/java/util/regex/MatchResultImpl.java2
-rw-r--r--luni/src/main/java/java/util/regex/Matcher.java199
-rw-r--r--luni/src/main/java/java/util/regex/Pattern.java2
-rw-r--r--luni/src/main/java/java/util/spi/CurrencyNameProvider.java49
-rw-r--r--luni/src/main/java/java/util/spi/LocaleNameProvider.java75
-rw-r--r--luni/src/main/java/java/util/spi/LocaleServiceProvider.java40
-rw-r--r--luni/src/main/java/java/util/spi/TimeZoneNameProvider.java51
-rw-r--r--luni/src/main/java/java/util/zip/Deflater.java8
-rw-r--r--luni/src/main/java/java/util/zip/DeflaterInputStream.java7
-rw-r--r--luni/src/main/java/java/util/zip/DeflaterOutputStream.java5
-rw-r--r--luni/src/main/java/java/util/zip/GZIPInputStream.java176
-rw-r--r--luni/src/main/java/java/util/zip/GZIPOutputStream.java7
-rw-r--r--luni/src/main/java/java/util/zip/Inflater.java9
-rw-r--r--luni/src/main/java/java/util/zip/InflaterInputStream.java7
-rw-r--r--luni/src/main/java/java/util/zip/ZipEntry.java72
-rw-r--r--luni/src/main/java/java/util/zip/ZipFile.java48
-rw-r--r--luni/src/main/java/java/util/zip/ZipInputStream.java41
-rw-r--r--luni/src/main/java/java/util/zip/ZipOutputStream.java61
-rw-r--r--luni/src/main/java/javax/crypto/Cipher.java358
-rw-r--r--luni/src/main/java/javax/crypto/CipherInputStream.java73
-rw-r--r--luni/src/main/java/javax/crypto/ExemptionMechanism.java1
-rw-r--r--luni/src/main/java/javax/crypto/KeyAgreement.java156
-rw-r--r--luni/src/main/java/javax/crypto/KeyGenerator.java3
-rw-r--r--luni/src/main/java/javax/crypto/Mac.java162
-rw-r--r--luni/src/main/java/javax/crypto/SealedObject.java72
-rw-r--r--luni/src/main/java/javax/crypto/SecretKeyFactory.java3
-rw-r--r--luni/src/main/java/javax/net/ssl/SSLContext.java142
-rw-r--r--luni/src/main/java/javax/net/ssl/SSLEngine.java623
-rw-r--r--luni/src/main/java/javax/net/ssl/SSLEngineResult.java8
-rw-r--r--luni/src/main/java/javax/net/ssl/SSLParameters.java1
-rw-r--r--luni/src/main/java/javax/net/ssl/SSLServerSocketFactory.java9
-rw-r--r--luni/src/main/java/javax/net/ssl/SSLSocket.java719
-rw-r--r--luni/src/main/java/javax/net/ssl/SSLSocketFactory.java47
-rw-r--r--luni/src/main/java/javax/security/cert/Certificate.java11
-rw-r--r--luni/src/main/java/javax/xml/transform/overview.html12
-rw-r--r--luni/src/main/java/libcore/icu/CollationElementIteratorICU.java9
-rw-r--r--luni/src/main/java/libcore/icu/DateIntervalFormat.java8
-rw-r--r--luni/src/main/java/libcore/icu/ICU.java277
-rw-r--r--luni/src/main/java/libcore/icu/LocaleData.java35
-rw-r--r--luni/src/main/java/libcore/icu/NativeBreakIterator.java18
-rw-r--r--luni/src/main/java/libcore/icu/NativeCollation.java9
-rw-r--r--luni/src/main/java/libcore/icu/NativeDecimalFormat.java195
-rw-r--r--luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java2
-rw-r--r--luni/src/main/java/libcore/icu/TimeZoneNames.java15
-rw-r--r--luni/src/main/java/libcore/io/BlockGuardOs.java140
-rw-r--r--luni/src/main/java/libcore/io/DeleteOnExit.java (renamed from luni/src/main/java/org/apache/harmony/luni/util/DeleteOnExit.java)18
-rw-r--r--luni/src/main/java/libcore/io/ErrnoException.java65
-rw-r--r--luni/src/main/java/libcore/io/ForwardingOs.java47
-rw-r--r--luni/src/main/java/libcore/io/GaiException.java66
-rw-r--r--luni/src/main/java/libcore/io/IoBridge.java105
-rw-r--r--luni/src/main/java/libcore/io/IoUtils.java4
-rw-r--r--luni/src/main/java/libcore/io/Memory.java57
-rw-r--r--luni/src/main/java/libcore/io/MemoryMappedFile.java6
-rw-r--r--luni/src/main/java/libcore/io/Os.java47
-rw-r--r--luni/src/main/java/libcore/io/Posix.java55
-rw-r--r--luni/src/main/java/libcore/io/Streams.java5
-rw-r--r--luni/src/main/java/libcore/io/StructPasswd.java38
-rw-r--r--luni/src/main/java/libcore/io/StructPollfd.java48
-rw-r--r--luni/src/main/java/libcore/io/StructStat.java90
-rw-r--r--luni/src/main/java/libcore/io/StructUtsname.java47
-rw-r--r--luni/src/main/java/libcore/net/MimeUtils.java48
-rw-r--r--luni/src/main/java/libcore/net/RawSocket.java142
-rw-r--r--luni/src/main/java/libcore/net/event/NetworkEventDispatcher.java79
-rw-r--r--luni/src/main/java/libcore/net/event/NetworkEventListener.java27
-rw-r--r--luni/src/main/java/libcore/net/url/FileURLConnection.java165
-rw-r--r--luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java10
-rw-r--r--luni/src/main/java/libcore/reflect/AnnotationAccess.java83
-rw-r--r--luni/src/main/java/libcore/reflect/GenericArrayTypeImpl.java15
-rw-r--r--luni/src/main/java/libcore/reflect/ParameterizedTypeImpl.java29
-rw-r--r--luni/src/main/java/libcore/util/EmptyArray.java2
-rw-r--r--luni/src/main/java/libcore/util/ZoneInfo.java693
-rw-r--r--luni/src/main/java/libcore/util/ZoneInfoDB.java71
-rw-r--r--luni/src/main/java/org/apache/harmony/security/fortress/Engine.java71
-rw-r--r--luni/src/main/java/org/apache/harmony/security/fortress/Services.java48
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/cert/Cache.java324
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/cert/DRLCertFactory.java44
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLEntryImpl.java179
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLImpl.java504
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java858
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java430
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java451
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/crypto/CryptoProvider.java49
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAKeyFactoryImpl.java217
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPrivateKeyImpl.java159
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPublicKeyImpl.java171
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1_MessageDigestImpl.java306
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1withDSA_SignatureImpl.java423
-rw-r--r--luni/src/main/java/org/apache/harmony/security/provider/crypto/ThreeIntegerSequence.java73
-rw-r--r--luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java105
-rw-r--r--luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java175
-rw-r--r--luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValue.java2
-rw-r--r--luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValueComparator.java13
-rw-r--r--luni/src/main/java/org/apache/harmony/security/x501/AttributeValue.java84
-rw-r--r--luni/src/main/java/org/apache/harmony/security/x509/Extension.java19
-rw-r--r--luni/src/main/java/org/apache/harmony/security/x509/Extensions.java8
-rw-r--r--luni/src/main/java/org/apache/harmony/security/x509/InvalidityDate.java7
-rw-r--r--luni/src/main/java/org/apache/harmony/security/x509/ReasonCode.java9
-rw-r--r--luni/src/main/java/org/apache/harmony/xml/ExpatParser.java6
311 files changed, 15995 insertions, 11652 deletions
diff --git a/luni/src/main/java/android/system/ErrnoException.java b/luni/src/main/java/android/system/ErrnoException.java
new file mode 100644
index 0000000..90155c8
--- /dev/null
+++ b/luni/src/main/java/android/system/ErrnoException.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import java.io.IOException;
+import java.net.SocketException;
+import libcore.io.Libcore;
+
+/**
+ * A checked exception thrown when {@link Os} methods fail. This exception contains the native
+ * errno value, for comparison against the constants in {@link OsConstants}, should sophisticated
+ * callers need to adjust their behavior based on the exact failure.
+ */
+public final class ErrnoException extends Exception {
+ private final String functionName;
+
+ /**
+ * The errno value, for comparison with the {@code E} constants in {@link OsConstants}.
+ */
+ public final int errno;
+
+ /**
+ * Constructs an instance with the given function name and errno value.
+ */
+ public ErrnoException(String functionName, int errno) {
+ this.functionName = functionName;
+ this.errno = errno;
+ }
+
+ /**
+ * Constructs an instance with the given function name, errno value, and cause.
+ */
+ public ErrnoException(String functionName, int errno, Throwable cause) {
+ super(cause);
+ this.functionName = functionName;
+ this.errno = errno;
+ }
+
+ /**
+ * Converts the stashed function name and errno value to a human-readable string.
+ * We do this here rather than in the constructor so that callers only pay for
+ * this if they need it.
+ */
+ @Override public String getMessage() {
+ String errnoName = OsConstants.errnoName(errno);
+ if (errnoName == null) {
+ errnoName = "errno " + errno;
+ }
+ String description = Libcore.os.strerror(errno);
+ return functionName + " failed: " + errnoName + " (" + description + ")";
+ }
+
+ /**
+ * @hide - internal use only.
+ */
+ public IOException rethrowAsIOException() throws IOException {
+ IOException newException = new IOException(getMessage());
+ newException.initCause(this);
+ throw newException;
+ }
+
+ /**
+ * @hide - internal use only.
+ */
+ public SocketException rethrowAsSocketException() throws SocketException {
+ throw new SocketException(getMessage(), this);
+ }
+}
diff --git a/luni/src/main/java/android/system/GaiException.java b/luni/src/main/java/android/system/GaiException.java
new file mode 100644
index 0000000..dc10566
--- /dev/null
+++ b/luni/src/main/java/android/system/GaiException.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import java.net.UnknownHostException;
+import libcore.io.Libcore;
+
+/**
+ * An unchecked exception thrown when {@code getaddrinfo} or {@code getnameinfo} fails.
+ * This exception contains the native {@link #error} value, should sophisticated
+ * callers need to adjust their behavior based on the exact failure.
+ *
+ * @hide
+ */
+public final class GaiException extends RuntimeException {
+ private final String functionName;
+
+ /**
+ * The native error value, for comparison with the {@code GAI_} constants in {@link OsConstants}.
+ */
+ public final int error;
+
+ /**
+ * Constructs an instance with the given function name and error value.
+ */
+ public GaiException(String functionName, int error) {
+ this.functionName = functionName;
+ this.error = error;
+ }
+
+ /**
+ * Constructs an instance with the given function name, error value, and cause.
+ */
+ public GaiException(String functionName, int error, Throwable cause) {
+ super(cause);
+ this.functionName = functionName;
+ this.error = error;
+ }
+
+ /**
+ * Converts the stashed function name and error value to a human-readable string.
+ * We do this here rather than in the constructor so that callers only pay for
+ * this if they need it.
+ */
+ @Override public String getMessage() {
+ String gaiName = OsConstants.gaiName(error);
+ if (gaiName == null) {
+ gaiName = "GAI_ error " + error;
+ }
+ String description = Libcore.os.gai_strerror(error);
+ return functionName + " failed: " + gaiName + " (" + description + ")";
+ }
+
+ /**
+ * @hide - internal use only.
+ */
+ public UnknownHostException rethrowAsUnknownHostException(String detailMessage) throws UnknownHostException {
+ UnknownHostException newException = new UnknownHostException(detailMessage);
+ newException.initCause(this);
+ throw newException;
+ }
+
+ /**
+ * @hide - internal use only.
+ */
+ public UnknownHostException rethrowAsUnknownHostException() throws UnknownHostException {
+ throw rethrowAsUnknownHostException(getMessage());
+ }
+}
diff --git a/luni/src/main/java/android/system/Os.java b/luni/src/main/java/android/system/Os.java
new file mode 100644
index 0000000..0b80b52
--- /dev/null
+++ b/luni/src/main/java/android/system/Os.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import android.system.ErrnoException;
+import android.system.GaiException;
+import android.system.StructAddrinfo;
+import android.system.StructFlock;
+import android.system.StructGroupReq;
+import android.system.StructGroupSourceReq;
+import android.system.StructLinger;
+import android.system.StructPasswd;
+import android.system.StructPollfd;
+import android.system.StructStat;
+import android.system.StructStatVfs;
+import android.system.StructTimeval;
+import android.system.StructUcred;
+import android.system.StructUtsname;
+import android.util.MutableInt;
+import android.util.MutableLong;
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import libcore.io.Libcore;
+
+/**
+ * Access to low-level system functionality. Most of these are system calls. Most users will want
+ * to use higher-level APIs where available, but this class provides access to the underlying
+ * primitives used to implement the higher-level APIs.
+ *
+ * <p>The corresponding constants can be found in {@link OsConstants}.
+ */
+public final class Os {
+ private Os() {}
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/accept.2.html">accept(2)</a>.
+ */
+ public static FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException { return Libcore.os.accept(fd, peerAddress); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/access.2.html">access(2)</a>.
+ */
+ public static boolean access(String path, int mode) throws ErrnoException { return Libcore.os.access(path, mode); }
+
+ /** @hide */ public static InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException { return Libcore.os.android_getaddrinfo(node, hints, netId); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/bind.2.html">bind(2)</a>.
+ */
+ public static void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.bind(fd, address, port); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/chmod.2.html">chmod(2)</a>.
+ */
+ public static void chmod(String path, int mode) throws ErrnoException { Libcore.os.chmod(path, mode); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/chown.2.html">chown(2)</a>.
+ */
+ public static void chown(String path, int uid, int gid) throws ErrnoException { Libcore.os.chown(path, uid, gid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/close.2.html">close(2)</a>.
+ */
+ public static void close(FileDescriptor fd) throws ErrnoException { Libcore.os.close(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/connect.2.html">connect(2)</a>.
+ */
+ public static void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.connect(fd, address, port); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/dup.2.html">dup(2)</a>.
+ */
+ public static FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException { return Libcore.os.dup(oldFd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/dup2.2.html">dup2(2)</a>.
+ */
+ public static FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException { return Libcore.os.dup2(oldFd, newFd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/environ.3.html">environ(3)</a>.
+ */
+ public static String[] environ() { return Libcore.os.environ(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/execv.2.html">execv(2)</a>.
+ */
+ public static void execv(String filename, String[] argv) throws ErrnoException { Libcore.os.execv(filename, argv); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/execve.2.html">execve(2)</a>.
+ */
+ public static void execve(String filename, String[] argv, String[] envp) throws ErrnoException { Libcore.os.execve(filename, argv, envp); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fchmod.2.html">fchmod(2)</a>.
+ */
+ public static void fchmod(FileDescriptor fd, int mode) throws ErrnoException { Libcore.os.fchmod(fd, mode); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fchown.2.html">fchown(2)</a>.
+ */
+ public static void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException { Libcore.os.fchown(fd, uid, gid); }
+
+ /** @hide */ public static int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return Libcore.os.fcntlVoid(fd, cmd); }
+ /** @hide */ public static int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException { return Libcore.os.fcntlLong(fd, cmd, arg); }
+ /** @hide */ public static int fcntlFlock(FileDescriptor fd, int cmd, StructFlock arg) throws ErrnoException { return Libcore.os.fcntlFlock(fd, cmd, arg); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fdatasync.2.html">fdatasync(2)</a>.
+ */
+ public static void fdatasync(FileDescriptor fd) throws ErrnoException { Libcore.os.fdatasync(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fstat.2.html">fstat(2)</a>.
+ */
+ public static StructStat fstat(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstat(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fstatvfs.2.html">fstatvfs(2)</a>.
+ */
+ public static StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstatvfs(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/fsync.2.html">fsync(2)</a>.
+ */
+ public static void fsync(FileDescriptor fd) throws ErrnoException { Libcore.os.fsync(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/ftruncate.2.html">ftruncate(2)</a>.
+ */
+ public static void ftruncate(FileDescriptor fd, long length) throws ErrnoException { Libcore.os.ftruncate(fd, length); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/gai_strerror.3.html">gai_strerror(3)</a>.
+ */
+ public static String gai_strerror(int error) { return Libcore.os.gai_strerror(error); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getegid.2.html">getegid(2)</a>.
+ */
+ public static int getegid() { return Libcore.os.getegid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/geteuid.2.html">geteuid(2)</a>.
+ */
+ public static int geteuid() { return Libcore.os.geteuid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getgid.2.html">getgid(2)</a>.
+ */
+ public static int getgid() { return Libcore.os.getgid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/getenv.3.html">getenv(3)</a>.
+ */
+ public static String getenv(String name) { return Libcore.os.getenv(name); }
+
+ /** @hide */ public static String getnameinfo(InetAddress address, int flags) throws GaiException { return Libcore.os.getnameinfo(address, flags); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getpeername.2.html">getpeername(2)</a>.
+ */
+ public static SocketAddress getpeername(FileDescriptor fd) throws ErrnoException { return Libcore.os.getpeername(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getpid.2.html">getpid(2)</a>.
+ */
+ public static int getpid() { return Libcore.os.getpid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getppid.2.html">getppid(2)</a>.
+ */
+ public static int getppid() { return Libcore.os.getppid(); }
+
+ /** @hide */ public static StructPasswd getpwnam(String name) throws ErrnoException { return Libcore.os.getpwnam(name); }
+
+ /** @hide */ public static StructPasswd getpwuid(int uid) throws ErrnoException { return Libcore.os.getpwuid(uid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getsockname.2.html">getsockname(2)</a>.
+ */
+ public static SocketAddress getsockname(FileDescriptor fd) throws ErrnoException { return Libcore.os.getsockname(fd); }
+
+ /** @hide */ public static int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptByte(fd, level, option); }
+ /** @hide */ public static InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInAddr(fd, level, option); }
+ /** @hide */ public static int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInt(fd, level, option); }
+ /** @hide */ public static StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptLinger(fd, level, option); }
+ /** @hide */ public static StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptTimeval(fd, level, option); }
+ /** @hide */ public static StructUcred getsockoptUcred(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptUcred(fd, level, option); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/gettid.2.html">gettid(2)</a>.
+ */
+ public static int gettid() { return Libcore.os.gettid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/getuid.2.html">getuid(2)</a>.
+ */
+ public static int getuid() { return Libcore.os.getuid(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/if_indextoname.3.html">if_indextoname(3)</a>.
+ */
+ public static String if_indextoname(int index) { return Libcore.os.if_indextoname(index); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/inet_pton.3.html">inet_pton(3)</a>.
+ */
+ public static InetAddress inet_pton(int family, String address) { return Libcore.os.inet_pton(family, address); }
+
+ /** @hide */ public static InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException { return Libcore.os.ioctlInetAddress(fd, cmd, interfaceName); }
+ /** @hide */ public static int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException { return Libcore.os.ioctlInt(fd, cmd, arg); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/isatty.3.html">isatty(3)</a>.
+ */
+ public static boolean isatty(FileDescriptor fd) { return Libcore.os.isatty(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/kill.2.html">kill(2)</a>.
+ */
+ public static void kill(int pid, int signal) throws ErrnoException { Libcore.os.kill(pid, signal); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/lchown.2.html">lchown(2)</a>.
+ */
+ public static void lchown(String path, int uid, int gid) throws ErrnoException { Libcore.os.lchown(path, uid, gid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/link.2.html">link(2)</a>.
+ */
+ public static void link(String oldPath, String newPath) throws ErrnoException { Libcore.os.link(oldPath, newPath); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/listen.2.html">listen(2)</a>.
+ */
+ public static void listen(FileDescriptor fd, int backlog) throws ErrnoException { Libcore.os.listen(fd, backlog); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/lseek.2.html">lseek(2)</a>.
+ */
+ public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return Libcore.os.lseek(fd, offset, whence); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/lstat.2.html">lstat(2)</a>.
+ */
+ public static StructStat lstat(String path) throws ErrnoException { return Libcore.os.lstat(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/mincore.2.html">mincore(2)</a>.
+ */
+ public static void mincore(long address, long byteCount, byte[] vector) throws ErrnoException { Libcore.os.mincore(address, byteCount, vector); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/mkdir.2.html">mkdir(2)</a>.
+ */
+ public static void mkdir(String path, int mode) throws ErrnoException { Libcore.os.mkdir(path, mode); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/mkfifo.3.html">mkfifo(3)</a>.
+ */
+ public static void mkfifo(String path, int mode) throws ErrnoException { Libcore.os.mkfifo(path, mode); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/mlock.2.html">mlock(2)</a>.
+ */
+ public static void mlock(long address, long byteCount) throws ErrnoException { Libcore.os.mlock(address, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/mmap.2.html">mmap(2)</a>.
+ */
+ public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return Libcore.os.mmap(address, byteCount, prot, flags, fd, offset); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/msync.2.html">msync(2)</a>.
+ */
+ public static void msync(long address, long byteCount, int flags) throws ErrnoException { Libcore.os.msync(address, byteCount, flags); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/munlock.2.html">munlock(2)</a>.
+ */
+ public static void munlock(long address, long byteCount) throws ErrnoException { Libcore.os.munlock(address, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/munmap.2.html">munmap(2)</a>.
+ */
+ public static void munmap(long address, long byteCount) throws ErrnoException { Libcore.os.munmap(address, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>.
+ */
+ public static FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return Libcore.os.open(path, flags, mode); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/pipe.2.html">pipe(2)</a>.
+ */
+ public static FileDescriptor[] pipe() throws ErrnoException { return Libcore.os.pipe(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/poll.2.html">poll(2)</a>.
+ */
+ public static int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return Libcore.os.poll(fds, timeoutMs); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/posix_fallocate.2.html">posix_fallocate(2)</a>.
+ */
+ public static void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { Libcore.os.posix_fallocate(fd, offset, length); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/prctl.2.html">prctl(2)</a>.
+ */
+ public static int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException { return Libcore.os.prctl(option, arg2, arg3, arg4, arg5); };
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>.
+ */
+ public static int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, buffer, offset); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>.
+ */
+ public static int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, bytes, byteOffset, byteCount, offset); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>.
+ */
+ public static int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, buffer, offset); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>.
+ */
+ public static int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, bytes, byteOffset, byteCount, offset); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
+ */
+ public static int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, buffer); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
+ */
+ public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, bytes, byteOffset, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/readlink.2.html">readlink(2)</a>.
+ */
+ public static String readlink(String path) throws ErrnoException { return Libcore.os.readlink(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/readv.2.html">readv(2)</a>.
+ */
+ public static int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.readv(fd, buffers, offsets, byteCounts); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>.
+ */
+ public static int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, buffer, flags, srcAddress); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>.
+ */
+ public static int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/remove.3.html">remove(3)</a>.
+ */
+ public static void remove(String path) throws ErrnoException { Libcore.os.remove(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/rename.2.html">rename(2)</a>.
+ */
+ public static void rename(String oldPath, String newPath) throws ErrnoException { Libcore.os.rename(oldPath, newPath); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
+ */
+ public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { return Libcore.os.sendfile(outFd, inFd, inOffset, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
+ */
+ public static int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, buffer, flags, inetAddress, port); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
+ */
+ public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setegid.2.html">setegid(2)</a>.
+ */
+ public static void setegid(int egid) throws ErrnoException { Libcore.os.setegid(egid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/setenv.3.html">setenv(3)</a>.
+ */
+ public static void setenv(String name, String value, boolean overwrite) throws ErrnoException { Libcore.os.setenv(name, value, overwrite); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/seteuid.2.html">seteuid(2)</a>.
+ */
+ public static void seteuid(int euid) throws ErrnoException { Libcore.os.seteuid(euid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setgid.2.html">setgid(2)</a>.
+ */
+ public static void setgid(int gid) throws ErrnoException { Libcore.os.setgid(gid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setsid.2.html">setsid(2)</a>.
+ */
+ public static int setsid() throws ErrnoException { return Libcore.os.setsid(); }
+
+ /** @hide */ public static void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptByte(fd, level, option, value); }
+ /** @hide */ public static void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException { Libcore.os.setsockoptIfreq(fd, level, option, value); }
+ /** @hide */ public static void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptInt(fd, level, option, value); }
+ /** @hide */ public static void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptIpMreqn(fd, level, option, value); }
+ /** @hide */ public static void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { Libcore.os.setsockoptGroupReq(fd, level, option, value); }
+ /** @hide */ public static void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException { Libcore.os.setsockoptGroupSourceReq(fd, level, option, value); }
+ /** @hide */ public static void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { Libcore.os.setsockoptLinger(fd, level, option, value); }
+ /** @hide */ public static void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { Libcore.os.setsockoptTimeval(fd, level, option, value); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/setuid.2.html">setuid(2)</a>.
+ */
+ public static void setuid(int uid) throws ErrnoException { Libcore.os.setuid(uid); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/shutdown.2.html">shutdown(2)</a>.
+ */
+ public static void shutdown(FileDescriptor fd, int how) throws ErrnoException { Libcore.os.shutdown(fd, how); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/socket.2.html">socket(2)</a>.
+ */
+ public static FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException { return Libcore.os.socket(domain, type, protocol); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/socketpair.2.html">socketpair(2)</a>.
+ */
+ public static void socketpair(int domain, int type, int protocol, FileDescriptor fd1, FileDescriptor fd2) throws ErrnoException { Libcore.os.socketpair(domain, type, protocol, fd1, fd2); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/stat.2.html">stat(2)</a>.
+ */
+ public static StructStat stat(String path) throws ErrnoException { return Libcore.os.stat(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/statvfs.2.html">statvfs(2)</a>.
+ */
+ public static StructStatVfs statvfs(String path) throws ErrnoException { return Libcore.os.statvfs(path); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/strerror.3.html">strerror(2)</a>.
+ */
+ public static String strerror(int errno) { return Libcore.os.strerror(errno); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/strsignal.3.html">strsignal(3)</a>.
+ */
+ public static String strsignal(int signal) { return Libcore.os.strsignal(signal); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/symlink.2.html">symlink(2)</a>.
+ */
+ public static void symlink(String oldPath, String newPath) throws ErrnoException { Libcore.os.symlink(oldPath, newPath); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/sysconf.3.html">sysconf(3)</a>.
+ */
+ public static long sysconf(int name) { return Libcore.os.sysconf(name); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/tcdrain.3.html">tcdrain(3)</a>.
+ */
+ public static void tcdrain(FileDescriptor fd) throws ErrnoException { Libcore.os.tcdrain(fd); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/tcsendbreak.3.html">tcsendbreak(3)</a>.
+ */
+ public static void tcsendbreak(FileDescriptor fd, int duration) throws ErrnoException { Libcore.os.tcsendbreak(fd, duration); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/umask.2.html">umask(2)</a>.
+ */
+ public static int umask(int mask) { return Libcore.os.umask(mask); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/uname.2.html">uname(2)</a>.
+ */
+ public static StructUtsname uname() { return Libcore.os.uname(); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man3/unsetenv.3.html">unsetenv(3)</a>.
+ */
+ public static void unsetenv(String name) throws ErrnoException { Libcore.os.unsetenv(name); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
+ */
+ public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return Libcore.os.waitpid(pid, status, options); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
+ */
+ public static int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, buffer); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
+ */
+ public static int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, bytes, byteOffset, byteCount); }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/writev.2.html">writev(2)</a>.
+ */
+ public static int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.writev(fd, buffers, offsets, byteCounts); }
+}
diff --git a/luni/src/main/java/libcore/io/OsConstants.java b/luni/src/main/java/android/system/OsConstants.java
index edecdd9..c758eb7 100644
--- a/luni/src/main/java/libcore/io/OsConstants.java
+++ b/luni/src/main/java/android/system/OsConstants.java
@@ -14,25 +14,83 @@
* limitations under the License.
*/
-package libcore.io;
+package android.system;
+/**
+ * Constants and helper functions for use with {@link Os}.
+ */
public final class OsConstants {
- private OsConstants() { }
+ private OsConstants() {
+ }
+ /**
+ * Tests whether the given mode is a block device.
+ */
public static boolean S_ISBLK(int mode) { return (mode & S_IFMT) == S_IFBLK; }
+
+ /**
+ * Tests whether the given mode is a character device.
+ */
public static boolean S_ISCHR(int mode) { return (mode & S_IFMT) == S_IFCHR; }
+
+ /**
+ * Tests whether the given mode is a directory.
+ */
public static boolean S_ISDIR(int mode) { return (mode & S_IFMT) == S_IFDIR; }
+
+ /**
+ * Tests whether the given mode is a FIFO.
+ */
public static boolean S_ISFIFO(int mode) { return (mode & S_IFMT) == S_IFIFO; }
+
+ /**
+ * Tests whether the given mode is a regular file.
+ */
public static boolean S_ISREG(int mode) { return (mode & S_IFMT) == S_IFREG; }
+
+ /**
+ * Tests whether the given mode is a symbolic link.
+ */
public static boolean S_ISLNK(int mode) { return (mode & S_IFMT) == S_IFLNK; }
+
+ /**
+ * Tests whether the given mode is a socket.
+ */
public static boolean S_ISSOCK(int mode) { return (mode & S_IFMT) == S_IFSOCK; }
+ /**
+ * Extracts the exit status of a child. Only valid if WIFEXITED returns true.
+ */
public static int WEXITSTATUS(int status) { return (status & 0xff00) >> 8; }
+
+ /**
+ * Tests whether the child dumped core. Only valid if WIFSIGNALED returns true.
+ */
public static boolean WCOREDUMP(int status) { return (status & 0x80) != 0; }
+
+ /**
+ * Returns the signal that caused the child to exit. Only valid if WIFSIGNALED returns true.
+ */
public static int WTERMSIG(int status) { return status & 0x7f; }
+
+ /**
+ * Returns the signal that cause the child to stop. Only valid if WIFSTOPPED returns true.
+ */
public static int WSTOPSIG(int status) { return WEXITSTATUS(status); }
+
+ /**
+ * Tests whether the child exited normally.
+ */
public static boolean WIFEXITED(int status) { return (WTERMSIG(status) == 0); }
+
+ /**
+ * Tests whether the child was stopped (not terminated) by a signal.
+ */
public static boolean WIFSTOPPED(int status) { return (WTERMSIG(status) == 0x7f); }
+
+ /**
+ * Tests whether the child was terminated by a signal.
+ */
public static boolean WIFSIGNALED(int status) { return (WTERMSIG(status + 1) >= 2); }
public static final int AF_INET = placeholder();
@@ -48,6 +106,7 @@ public final class OsConstants {
public static final int AI_V4MAPPED = placeholder();
public static final int CAP_AUDIT_CONTROL = placeholder();
public static final int CAP_AUDIT_WRITE = placeholder();
+ public static final int CAP_BLOCK_SUSPEND = placeholder();
public static final int CAP_CHOWN = placeholder();
public static final int CAP_DAC_OVERRIDE = placeholder();
public static final int CAP_DAC_READ_SEARCH = placeholder();
@@ -194,6 +253,15 @@ public final class OsConstants {
public static final int F_SETOWN = placeholder();
public static final int F_UNLCK = placeholder();
public static final int F_WRLCK = placeholder();
+ public static final int IFA_F_DADFAILED = placeholder();
+ public static final int IFA_F_DEPRECATED = placeholder();
+ public static final int IFA_F_HOMEADDRESS = placeholder();
+ public static final int IFA_F_NODAD = placeholder();
+ public static final int IFA_F_OPTIMISTIC = placeholder();
+ public static final int IFA_F_PERMANENT = placeholder();
+ public static final int IFA_F_SECONDARY = placeholder();
+ public static final int IFA_F_TEMPORARY = placeholder();
+ public static final int IFA_F_TENTATIVE = placeholder();
public static final int IFF_ALLMULTI = placeholder();
public static final int IFF_AUTOMEDIA = placeholder();
public static final int IFF_BROADCAST = placeholder();
@@ -240,6 +308,10 @@ public final class OsConstants {
public static final int MAP_SHARED = placeholder();
public static final int MCAST_JOIN_GROUP = placeholder();
public static final int MCAST_LEAVE_GROUP = placeholder();
+ public static final int MCAST_JOIN_SOURCE_GROUP = placeholder();
+ public static final int MCAST_LEAVE_SOURCE_GROUP = placeholder();
+ public static final int MCAST_BLOCK_SOURCE = placeholder();
+ public static final int MCAST_UNBLOCK_SOURCE = placeholder();
public static final int MCL_CURRENT = placeholder();
public static final int MCL_FUTURE = placeholder();
public static final int MSG_CTRUNC = placeholder();
@@ -279,11 +351,19 @@ public final class OsConstants {
public static final int POLLRDNORM = placeholder();
public static final int POLLWRBAND = placeholder();
public static final int POLLWRNORM = placeholder();
+ public static final int PR_GET_DUMPABLE = placeholder();
+ public static final int PR_SET_DUMPABLE = placeholder();
+ public static final int PR_SET_NO_NEW_PRIVS = placeholder();
public static final int PROT_EXEC = placeholder();
public static final int PROT_NONE = placeholder();
public static final int PROT_READ = placeholder();
public static final int PROT_WRITE = placeholder();
public static final int R_OK = placeholder();
+ public static final int RT_SCOPE_HOST = placeholder();
+ public static final int RT_SCOPE_LINK = placeholder();
+ public static final int RT_SCOPE_NOWHERE = placeholder();
+ public static final int RT_SCOPE_SITE = placeholder();
+ public static final int RT_SCOPE_UNIVERSE = placeholder();
public static final int SEEK_CUR = placeholder();
public static final int SEEK_END = placeholder();
public static final int SEEK_SET = placeholder();
@@ -476,6 +556,10 @@ public final class OsConstants {
public static final int _SC_XOPEN_VERSION = placeholder();
public static final int _SC_XOPEN_XCU_VERSION = placeholder();
+ /**
+ * Returns the string name of a getaddrinfo(3) error value.
+ * For example, "EAI_AGAIN".
+ */
public static String gaiName(int error) {
if (error == EAI_AGAIN) {
return "EAI_AGAIN";
@@ -513,6 +597,10 @@ public final class OsConstants {
return null;
}
+ /**
+ * Returns the string name of an errno value.
+ * For example, "EACCES". See {@link Os#strerror} for human-readable errno descriptions.
+ */
public static String errnoName(int errno) {
if (errno == E2BIG) {
return "E2BIG";
diff --git a/luni/src/main/java/libcore/io/StructAddrinfo.java b/luni/src/main/java/android/system/StructAddrinfo.java
index 8c8181d..2425946 100644
--- a/luni/src/main/java/libcore/io/StructAddrinfo.java
+++ b/luni/src/main/java/android/system/StructAddrinfo.java
@@ -14,38 +14,45 @@
* limitations under the License.
*/
-package libcore.io;
+package android.system;
import java.net.InetAddress;
+import libcore.util.Objects;
/**
* Information returned/taken by getaddrinfo(3). Corresponds to C's {@code struct addrinfo} from
* <a href="http://pubs.opengroup.org/onlinepubs/009695399/basedefs/netdb.h.html">&lt;netdb.h&gt;</a>
*
* TODO: we currently only _take_ a StructAddrinfo; getaddrinfo returns an InetAddress[].
+ *
+ * @hide
*/
public final class StructAddrinfo {
- /** Flags describing the kind of lookup to be done. (Such as AI_ADDRCONFIG.) */
- public int ai_flags;
+ /** Flags describing the kind of lookup to be done. (Such as AI_ADDRCONFIG.) */
+ public int ai_flags;
+
+ /** Desired address family for results. (Such as AF_INET6 for IPv6. AF_UNSPEC means "any".) */
+ public int ai_family;
- /** Desired address family for results. (Such as AF_INET6 for IPv6. AF_UNSPEC means "any".) */
- public int ai_family;
+ /** Socket type. (Such as SOCK_DGRAM. 0 means "any".) */
+ public int ai_socktype;
- /** Socket type. (Such as SOCK_DGRAM. 0 means "any".) */
- public int ai_socktype;
+ /** Protocol. (Such as IPPROTO_IPV6 IPv6. 0 means "any".) */
+ public int ai_protocol;
- /** Protocol. (Such as IPPROTO_IPV6 IPv6. 0 means "any".) */
- public int ai_protocol;
+ /** Address length. (Not useful in Java.) */
+ // public int ai_addrlen;
- /** Address length. (Not useful in Java.) */
- // public int ai_addrlen;
+ /** Address. */
+ public InetAddress ai_addr;
- /** Address. */
- public InetAddress ai_addr;
+ /** Canonical name of service location (if AI_CANONNAME provided in ai_flags). */
+ // public String ai_canonname;
- /** Canonical name of service location (if AI_CANONNAME provided in ai_flags). */
- // public String ai_canonname;
+ /** Next element in linked list. */
+ public StructAddrinfo ai_next;
- /** Next element in linked list. */
- public StructAddrinfo ai_next;
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/luni/src/main/java/libcore/io/StructFlock.java b/luni/src/main/java/android/system/StructFlock.java
index 11c29df..92cd95a 100644
--- a/luni/src/main/java/libcore/io/StructFlock.java
+++ b/luni/src/main/java/android/system/StructFlock.java
@@ -14,26 +14,34 @@
* limitations under the License.
*/
-package libcore.io;
+package android.system;
+
+import libcore.util.Objects;
/**
* Information returned/taken by fcntl(2) F_GETFL and F_SETFL. Corresponds to C's
* {@code struct flock} from
* <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/fcntl.h.html">&lt;fcntl.h&gt;</a>
+ *
+ * @hide
*/
public final class StructFlock {
- /** The operation type, one of F_RDLCK, F_WRLCK, or F_UNLCK. */
- public short l_type;
+ /** The operation type, one of F_RDLCK, F_WRLCK, or F_UNLCK. */
+ public short l_type;
+
+ /** How to interpret l_start, one of SEEK_CUR, SEEK_END, SEEK_SET. */
+ public short l_whence;
- /** How to interpret l_start, one of SEEK_CUR, SEEK_END, SEEK_SET. */
- public short l_whence;
+ /** Start offset. */
+ public long l_start; /*off_t*/
- /** Start offset. */
- public long l_start; /*off_t*/
+ /** Byte count to operate on. */
+ public long l_len; /*off_t*/
- /** Byte count to operate on. */
- public long l_len; /*off_t*/
+ /** Process blocking our lock (filled in by F_GETLK, otherwise unused). */
+ public int l_pid; /*pid_t*/
- /** Process blocking our lock (filled in by F_GETLK, otherwise unused). */
- public int l_pid; /*pid_t*/
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/luni/src/main/java/libcore/io/StructGroupReq.java b/luni/src/main/java/android/system/StructGroupReq.java
index 0bdf783..8ed5950 100644
--- a/luni/src/main/java/libcore/io/StructGroupReq.java
+++ b/luni/src/main/java/android/system/StructGroupReq.java
@@ -14,23 +14,26 @@
* limitations under the License.
*/
-package libcore.io;
+package android.system;
import java.net.InetAddress;
+import libcore.util.Objects;
/**
* Corresponds to C's {@code struct group_req}.
+ *
+ * @hide
*/
public final class StructGroupReq {
- public final int gr_interface;
- public final InetAddress gr_group;
+ public final int gr_interface;
+ public final InetAddress gr_group;
- public StructGroupReq(int gr_interface, InetAddress gr_group) {
- this.gr_interface = gr_interface;
- this.gr_group = gr_group;
- }
+ public StructGroupReq(int gr_interface, InetAddress gr_group) {
+ this.gr_interface = gr_interface;
+ this.gr_group = gr_group;
+ }
- @Override public String toString() {
- return "StructGroupReq[gr_interface=" + gr_interface + ",gr_group=" + gr_group + "]";
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/luni/src/main/java/android/system/StructGroupSourceReq.java b/luni/src/main/java/android/system/StructGroupSourceReq.java
new file mode 100644
index 0000000..c300338
--- /dev/null
+++ b/luni/src/main/java/android/system/StructGroupSourceReq.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import java.net.InetAddress;
+import libcore.util.Objects;
+
+/**
+ * Corresponds to C's {@code struct group_source_req}.
+ *
+ * @hide
+ */
+public final class StructGroupSourceReq {
+ public final int gsr_interface;
+ public final InetAddress gsr_group;
+ public final InetAddress gsr_source;
+
+ public StructGroupSourceReq(int gsr_interface, InetAddress gsr_group, InetAddress gsr_source) {
+ this.gsr_interface = gsr_interface;
+ this.gsr_group = gsr_group;
+ this.gsr_source = gsr_source;
+ }
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
+}
diff --git a/luni/src/main/java/libcore/io/StructLinger.java b/luni/src/main/java/android/system/StructLinger.java
index 9f149af..55ffc5c 100644
--- a/luni/src/main/java/libcore/io/StructLinger.java
+++ b/luni/src/main/java/android/system/StructLinger.java
@@ -14,29 +14,33 @@
* limitations under the License.
*/
-package libcore.io;
+package android.system;
+
+import libcore.util.Objects;
/**
* Corresponds to C's {@code struct linger} from
* <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_socket.h.html">&lt;sys/socket.h&gt;</a>
+ *
+ * @hide
*/
public final class StructLinger {
- /** Whether or not linger is enabled. Non-zero is on. */
- public final int l_onoff;
+ /** Whether or not linger is enabled. Non-zero is on. */
+ public final int l_onoff;
- /** Linger time in seconds. */
- public final int l_linger;
+ /** Linger time in seconds. */
+ public final int l_linger;
- public StructLinger(int l_onoff, int l_linger) {
- this.l_onoff = l_onoff;
- this.l_linger = l_linger;
- }
+ public StructLinger(int l_onoff, int l_linger) {
+ this.l_onoff = l_onoff;
+ this.l_linger = l_linger;
+ }
- public boolean isOn() {
- return l_onoff != 0;
- }
+ public boolean isOn() {
+ return l_onoff != 0;
+ }
- @Override public String toString() {
- return "StructLinger[l_onoff=" + l_onoff + ",l_linger=" + l_linger + "]";
- }
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/luni/src/main/java/android/system/StructPasswd.java b/luni/src/main/java/android/system/StructPasswd.java
new file mode 100644
index 0000000..04a7826
--- /dev/null
+++ b/luni/src/main/java/android/system/StructPasswd.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import libcore.util.Objects;
+
+/**
+ * Information returned by {@link Os#getpwnam} and {@link Os#getpwuid}. Corresponds to C's
+ * {@code struct passwd} from {@code &lt;pwd.h&gt;}.
+ *
+ * @hide
+ */
+public final class StructPasswd {
+ public final String pw_name;
+ public final int pw_uid;
+ public final int pw_gid;
+ public final String pw_dir;
+ public final String pw_shell;
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructPasswd(String pw_name, int pw_uid, int pw_gid, String pw_dir, String pw_shell) {
+ this.pw_name = pw_name;
+ this.pw_uid = pw_uid;
+ this.pw_gid = pw_gid;
+ this.pw_dir = pw_dir;
+ this.pw_shell = pw_shell;
+ }
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
+}
diff --git a/luni/src/main/java/android/system/StructPollfd.java b/luni/src/main/java/android/system/StructPollfd.java
new file mode 100644
index 0000000..b812612
--- /dev/null
+++ b/luni/src/main/java/android/system/StructPollfd.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import java.io.FileDescriptor;
+import libcore.util.Objects;
+
+/**
+ * Used as an in/out parameter to {@link Os#poll}.
+ * Corresponds to C's {@code struct pollfd} from {@code &lt;poll.h&gt;}.
+ */
+public final class StructPollfd {
+ /** The file descriptor to poll. */
+ public FileDescriptor fd;
+
+ /**
+ * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
+ * POLLOUT to the write fd set.
+ */
+ public short events;
+
+ /** The events that actually happened. */
+ public short revents;
+
+ /**
+ * A non-standard extension that lets callers conveniently map back to the object
+ * their fd belongs to. This is used by Selector, for example, to associate each
+ * FileDescriptor with the corresponding SelectionKey.
+ */
+ public Object userData;
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
+}
diff --git a/luni/src/main/java/android/system/StructStat.java b/luni/src/main/java/android/system/StructStat.java
new file mode 100644
index 0000000..a6958c1
--- /dev/null
+++ b/luni/src/main/java/android/system/StructStat.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import libcore.util.Objects;
+
+/**
+ * File information returned by {@link Os#fstat}, {@link Os#lstat}, and {@link Os#stat}.
+ * Corresponds to C's {@code struct stat} from {@code &lt;stat.h&gt;}.
+ */
+public final class StructStat {
+ /** Device ID of device containing file. */
+ public final long st_dev; /*dev_t*/
+
+ /** File serial number (inode). */
+ public final long st_ino; /*ino_t*/
+
+ /** Mode (permissions) of file. */
+ public final int st_mode; /*mode_t*/
+
+ /** Number of hard links to the file. */
+ public final long st_nlink; /*nlink_t*/
+
+ /** User ID of file. */
+ public final int st_uid; /*uid_t*/
+
+ /** Group ID of file. */
+ public final int st_gid; /*gid_t*/
+
+ /** Device ID (if file is character or block special). */
+ public final long st_rdev; /*dev_t*/
+
+ /**
+ * For regular files, the file size in bytes.
+ * For symbolic links, the length in bytes of the pathname contained in the symbolic link.
+ * For a shared memory object, the length in bytes.
+ * For a typed memory object, the length in bytes.
+ * For other file types, the use of this field is unspecified.
+ */
+ public final long st_size; /*off_t*/
+
+ /** Time of last access. */
+ public final long st_atime; /*time_t*/
+
+ /** Time of last data modification. */
+ public final long st_mtime; /*time_t*/
+
+ /** Time of last status change. */
+ public final long st_ctime; /*time_t*/
+
+ /**
+ * A file system-specific preferred I/O block size for this object.
+ * For some file system types, this may vary from file to file.
+ */
+ public final long st_blksize; /*blksize_t*/
+
+ /** Number of blocks allocated for this object. */
+ public final long st_blocks; /*blkcnt_t*/
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+ long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime,
+ long st_blksize, long st_blocks) {
+ this.st_dev = st_dev;
+ this.st_ino = st_ino;
+ this.st_mode = st_mode;
+ this.st_nlink = st_nlink;
+ this.st_uid = st_uid;
+ this.st_gid = st_gid;
+ this.st_rdev = st_rdev;
+ this.st_size = st_size;
+ this.st_atime = st_atime;
+ this.st_mtime = st_mtime;
+ this.st_ctime = st_ctime;
+ this.st_blksize = st_blksize;
+ this.st_blocks = st_blocks;
+ }
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
+}
diff --git a/luni/src/main/java/libcore/io/StructStatVfs.java b/luni/src/main/java/android/system/StructStatVfs.java
index bb78ff2..942a39a 100644
--- a/luni/src/main/java/libcore/io/StructStatVfs.java
+++ b/luni/src/main/java/android/system/StructStatVfs.java
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package libcore.io;
+package android.system;
+
+import libcore.util.Objects;
/**
- * File information returned by fstatvfs(2) and statvfs(2).
+ * File information returned by {@link Os#fstatvfs} and {@link Os#statvfs}.
*/
public final class StructStatVfs {
/** File system block size (used for block counts). */
@@ -53,7 +55,10 @@ public final class StructStatVfs {
/** Maximum filename length. */
public final long f_namemax; /*unsigned long*/
- StructStatVfs(long f_bsize, long f_frsize, long f_blocks, long f_bfree, long f_bavail,
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructStatVfs(long f_bsize, long f_frsize, long f_blocks, long f_bfree, long f_bavail,
long f_files, long f_ffree, long f_favail,
long f_fsid, long f_flag, long f_namemax) {
this.f_bsize = f_bsize;
@@ -68,4 +73,8 @@ public final class StructStatVfs {
this.f_flag = f_flag;
this.f_namemax = f_namemax;
}
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/luni/src/main/java/libcore/io/StructTimeval.java b/luni/src/main/java/android/system/StructTimeval.java
index 0ed3509..8a155b4 100644
--- a/luni/src/main/java/libcore/io/StructTimeval.java
+++ b/luni/src/main/java/android/system/StructTimeval.java
@@ -14,35 +14,39 @@
* limitations under the License.
*/
-package libcore.io;
+package android.system;
+
+import libcore.util.Objects;
/**
* Corresponds to C's {@code struct timeval} from
* <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_time.h.html">&lt;sys/time.h&gt;</a>
+ *
+ * @hide
*/
public final class StructTimeval {
- /** Seconds. */
- public final long tv_sec;
-
- /** Microseconds. */
- public final long tv_usec;
-
- private StructTimeval(long tv_sec, long tv_usec) {
- this.tv_sec = tv_sec;
- this.tv_usec = tv_usec;
- }
-
- public static StructTimeval fromMillis(long millis) {
- long tv_sec = millis / 1000;
- long tv_usec = (millis - (tv_sec * 1000)) * 1000;
- return new StructTimeval(tv_sec, tv_usec);
- }
-
- public long toMillis() {
- return (tv_sec * 1000) + (tv_usec / 1000);
- }
-
- @Override public String toString() {
- return "StructTimeval[tv_sec=" + tv_sec + ",tv_usec=" + tv_usec + "]";
- }
+ /** Seconds. */
+ public final long tv_sec;
+
+ /** Microseconds. */
+ public final long tv_usec;
+
+ private StructTimeval(long tv_sec, long tv_usec) {
+ this.tv_sec = tv_sec;
+ this.tv_usec = tv_usec;
+ }
+
+ public static StructTimeval fromMillis(long millis) {
+ long tv_sec = millis / 1000;
+ long tv_usec = (millis - (tv_sec * 1000)) * 1000;
+ return new StructTimeval(tv_sec, tv_usec);
+ }
+
+ public long toMillis() {
+ return (tv_sec * 1000) + (tv_usec / 1000);
+ }
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
}
diff --git a/luni/src/main/java/libcore/io/StructUcred.java b/luni/src/main/java/android/system/StructUcred.java
index 359995d..a1e3cd6 100644
--- a/luni/src/main/java/libcore/io/StructUcred.java
+++ b/luni/src/main/java/android/system/StructUcred.java
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package libcore.io;
+package android.system;
+
+import libcore.util.Objects;
/**
* Corresponds to C's {@code struct ucred}.
+ *
+ * @hide
*/
public final class StructUcred {
/** The peer's process id. */
@@ -29,13 +33,13 @@ public final class StructUcred {
/** The peer process' gid. */
public final int gid;
- private StructUcred(int pid, int uid, int gid) {
+ public StructUcred(int pid, int uid, int gid) {
this.pid = pid;
this.uid = uid;
this.gid = gid;
}
@Override public String toString() {
- return "StructUcred[pid=" + pid + ",uid=" + uid + ",gid=" + gid + "]";
+ return Objects.toString(this);
}
}
diff --git a/luni/src/main/java/android/system/StructUtsname.java b/luni/src/main/java/android/system/StructUtsname.java
new file mode 100644
index 0000000..5d9127b
--- /dev/null
+++ b/luni/src/main/java/android/system/StructUtsname.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import libcore.util.Objects;
+
+/**
+ * Information returned by {@link Os#uname}.
+ * Corresponds to C's {@code struct utsname} from {@code &lt;sys/utsname.h&gt;}.
+ */
+public final class StructUtsname {
+ /** The OS name, such as "Linux". */
+ public final String sysname;
+
+ /** The machine's unqualified name on some implementation-defined network. */
+ public final String nodename;
+
+ /** The OS release, such as "2.6.35-27-generic". */
+ public final String release;
+
+ /** The OS version, such as "#48-Ubuntu SMP Tue Feb 22 20:25:29 UTC 2011". */
+ public final String version;
+
+ /** The machine architecture, such as "armv7l" or "x86_64". */
+ public final String machine;
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructUtsname(String sysname, String nodename, String release, String version, String machine) {
+ this.sysname = sysname;
+ this.nodename = nodename;
+ this.release = release;
+ this.version = version;
+ this.machine = machine;
+ }
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
+}
diff --git a/luni/src/main/java/libcore/util/MutableBoolean.java b/luni/src/main/java/android/util/MutableBoolean.java
index 359a8f9..5a8a200 100644
--- a/luni/src/main/java/libcore/util/MutableBoolean.java
+++ b/luni/src/main/java/android/util/MutableBoolean.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package libcore.util;
+package android.util;
+/**
+ */
public final class MutableBoolean {
- public boolean value;
+ public boolean value;
- public MutableBoolean(boolean value) {
- this.value = value;
- }
+ public MutableBoolean(boolean value) {
+ this.value = value;
+ }
}
diff --git a/luni/src/main/java/libcore/util/MutableByte.java b/luni/src/main/java/android/util/MutableByte.java
index 13f780b..7397ba4 100644
--- a/luni/src/main/java/libcore/util/MutableByte.java
+++ b/luni/src/main/java/android/util/MutableByte.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package libcore.util;
+package android.util;
+/**
+ */
public final class MutableByte {
- public byte value;
+ public byte value;
- public MutableByte(byte value) {
- this.value = value;
- }
+ public MutableByte(byte value) {
+ this.value = value;
+ }
}
diff --git a/luni/src/main/java/libcore/util/MutableChar.java b/luni/src/main/java/android/util/MutableChar.java
index 1cafc3c..f435331 100644
--- a/luni/src/main/java/libcore/util/MutableChar.java
+++ b/luni/src/main/java/android/util/MutableChar.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package libcore.util;
+package android.util;
+/**
+ */
public final class MutableChar {
- public char value;
+ public char value;
- public MutableChar(char value) {
- this.value = value;
- }
+ public MutableChar(char value) {
+ this.value = value;
+ }
}
diff --git a/luni/src/main/java/libcore/util/MutableDouble.java b/luni/src/main/java/android/util/MutableDouble.java
index 4473ae6..f62f47e 100644
--- a/luni/src/main/java/libcore/util/MutableDouble.java
+++ b/luni/src/main/java/android/util/MutableDouble.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package libcore.util;
+package android.util;
+/**
+ */
public final class MutableDouble {
- public double value;
+ public double value;
- public MutableDouble(double value) {
- this.value = value;
- }
+ public MutableDouble(double value) {
+ this.value = value;
+ }
}
diff --git a/luni/src/main/java/libcore/util/MutableFloat.java b/luni/src/main/java/android/util/MutableFloat.java
index f81fba5..6b5441c 100644
--- a/luni/src/main/java/libcore/util/MutableFloat.java
+++ b/luni/src/main/java/android/util/MutableFloat.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package libcore.util;
+package android.util;
+/**
+ */
public final class MutableFloat {
- public float value;
+ public float value;
- public MutableFloat(float value) {
- this.value = value;
- }
+ public MutableFloat(float value) {
+ this.value = value;
+ }
}
diff --git a/luni/src/main/java/libcore/util/MutableInt.java b/luni/src/main/java/android/util/MutableInt.java
index c8feb3a..2f93030 100644
--- a/luni/src/main/java/libcore/util/MutableInt.java
+++ b/luni/src/main/java/android/util/MutableInt.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package libcore.util;
+package android.util;
+/**
+ */
public final class MutableInt {
- public int value;
+ public int value;
- public MutableInt(int value) {
- this.value = value;
- }
+ public MutableInt(int value) {
+ this.value = value;
+ }
}
diff --git a/luni/src/main/java/libcore/util/MutableLong.java b/luni/src/main/java/android/util/MutableLong.java
index ad9b78e..94beab5 100644
--- a/luni/src/main/java/libcore/util/MutableLong.java
+++ b/luni/src/main/java/android/util/MutableLong.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package libcore.util;
+package android.util;
+/**
+ */
public final class MutableLong {
- public long value;
+ public long value;
- public MutableLong(long value) {
- this.value = value;
- }
+ public MutableLong(long value) {
+ this.value = value;
+ }
}
diff --git a/luni/src/main/java/libcore/util/MutableShort.java b/luni/src/main/java/android/util/MutableShort.java
index 78b4c33..cdd9923 100644
--- a/luni/src/main/java/libcore/util/MutableShort.java
+++ b/luni/src/main/java/android/util/MutableShort.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package libcore.util;
+package android.util;
+/**
+ */
public final class MutableShort {
- public short value;
+ public short value;
- public MutableShort(short value) {
- this.value = value;
- }
+ public MutableShort(short value) {
+ this.value = value;
+ }
}
diff --git a/luni/src/main/java/java/io/BufferedInputStream.java b/luni/src/main/java/java/io/BufferedInputStream.java
index 5a810e7..85236b6 100644
--- a/luni/src/main/java/java/io/BufferedInputStream.java
+++ b/luni/src/main/java/java/io/BufferedInputStream.java
@@ -37,6 +37,13 @@ import java.util.Arrays;
*/
public class BufferedInputStream extends FilterInputStream {
/**
+ * The default buffer size if it is not specified.
+ *
+ * @hide
+ */
+ public static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ /**
* The buffer containing the current bytes read from the target InputStream.
*/
protected volatile byte[] buf;
@@ -73,7 +80,7 @@ public class BufferedInputStream extends FilterInputStream {
* @param in the {@code InputStream} the buffer reads from.
*/
public BufferedInputStream(InputStream in) {
- this(in, 8192);
+ this(in, DEFAULT_BUFFER_SIZE);
}
/**
@@ -325,7 +332,7 @@ public class BufferedInputStream extends FilterInputStream {
if (buf == null) {
throw new IOException("Stream is closed");
}
- if (-1 == markpos) {
+ if (markpos == -1) {
throw new IOException("Mark has been invalidated.");
}
pos = markpos;
diff --git a/luni/src/main/java/java/io/Console.java b/luni/src/main/java/java/io/Console.java
index a1d2097..fe07694c 100644
--- a/luni/src/main/java/java/io/Console.java
+++ b/luni/src/main/java/java/io/Console.java
@@ -16,9 +16,7 @@
package java.io;
import java.util.Formatter;
-import libcore.io.ErrnoException;
import libcore.io.Libcore;
-import static libcore.io.OsConstants.*;
/**
* Provides access to the console, if available. The system-wide instance can
@@ -48,12 +46,12 @@ public final class Console implements Flushable {
}
try {
return new Console(System.in, System.out);
- } catch (IOException ex) {
+ } catch (UnsupportedEncodingException ex) {
throw new AssertionError(ex);
}
}
- private Console(InputStream in, OutputStream out) throws IOException {
+ private Console(InputStream in, OutputStream out) throws UnsupportedEncodingException {
this.reader = new ConsoleReader(in);
this.writer = new ConsoleWriter(out);
}
@@ -129,48 +127,17 @@ public final class Console implements Flushable {
}
/**
- * Reads a password from the console. The password will not be echoed to the display.
- *
- * @return a character array containing the password, or null at EOF.
+ * This method is unimplemented on Android.
*/
public char[] readPassword() {
- synchronized (CONSOLE_LOCK) {
- int previousState = setEcho(false, 0);
- try {
- String password = readLine();
- writer.println(); // We won't have echoed the user's newline.
- return (password == null) ? null : password.toCharArray();
- } finally {
- setEcho(true, previousState);
- }
- }
- }
-
- private static int setEcho(boolean on, int previousState) {
- try {
- return setEchoImpl(on, previousState);
- } catch (IOException ex) {
- throw new IOError(ex);
- }
+ throw new UnsupportedOperationException();
}
- private static native int setEchoImpl(boolean on, int previousState) throws IOException;
/**
- * Reads a password from the console. The password will not be echoed to the display.
- * A formatted prompt is also displayed.
- *
- * @param format the format string (see {@link java.util.Formatter#format})
- * @param args
- * the list of arguments passed to the formatter. If there are
- * more arguments than required by {@code format},
- * additional arguments are ignored.
- * @return a character array containing the password, or null at EOF.
+ * This method is unimplemented on Android.
*/
public char[] readPassword(String format, Object... args) {
- synchronized (CONSOLE_LOCK) {
- format(format, args);
- return readPassword();
- }
+ throw new UnsupportedOperationException();
}
/**
@@ -181,7 +148,7 @@ public final class Console implements Flushable {
}
private static class ConsoleReader extends BufferedReader {
- public ConsoleReader(InputStream in) throws IOException {
+ public ConsoleReader(InputStream in) throws UnsupportedEncodingException {
super(new InputStreamReader(in, System.getProperty("file.encoding")), 256);
lock = CONSOLE_LOCK;
}
diff --git a/luni/src/main/java/java/io/File.java b/luni/src/main/java/java/io/File.java
index a8b4810..d107c28 100644
--- a/luni/src/main/java/java/io/File.java
+++ b/luni/src/main/java/java/io/File.java
@@ -17,19 +17,19 @@
package java.io;
+import android.system.ErrnoException;
+import android.system.StructStat;
+import android.system.StructStatVfs;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
-import libcore.io.ErrnoException;
+import libcore.io.DeleteOnExit;
import libcore.io.IoUtils;
import libcore.io.Libcore;
-import libcore.io.StructStat;
-import libcore.io.StructStatVfs;
-import org.apache.harmony.luni.util.DeleteOnExit;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* An "abstract" representation of a file system entity identified by a
@@ -411,15 +411,10 @@ public class File implements Serializable, Comparable<File> {
* if an I/O error occurs.
*/
public String getCanonicalPath() throws IOException {
- return realpath(getAbsolutePath());
+ return canonicalizePath(getAbsolutePath());
}
- /**
- * TODO: move this stuff to libcore.os.
- * @hide
- */
- private static native String realpath(String path);
- private static native String readlink(String path);
+ private static native String canonicalizePath(String path);
/**
* Returns a new file created using the canonical path of this file.
diff --git a/luni/src/main/java/java/io/FileDescriptor.java b/luni/src/main/java/java/io/FileDescriptor.java
index e4eb06c..eba0e4d 100644
--- a/luni/src/main/java/java/io/FileDescriptor.java
+++ b/luni/src/main/java/java/io/FileDescriptor.java
@@ -17,9 +17,9 @@
package java.io;
-import libcore.io.ErrnoException;
+import android.system.ErrnoException;
import libcore.io.Libcore;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* Wraps a Unix file descriptor. It's possible to get the file descriptor used by some
@@ -105,6 +105,15 @@ public final class FileDescriptor {
this.descriptor = fd;
}
+ /**
+ * @hide internal use only
+ */
+ public boolean isSocket() {
+ return isSocket(descriptor);
+ }
+
+ private static native boolean isSocket(int fd);
+
@Override public String toString() {
return "FileDescriptor[" + descriptor + "]";
}
diff --git a/luni/src/main/java/java/io/FileInputStream.java b/luni/src/main/java/java/io/FileInputStream.java
index b2e620f..7944ef1 100644
--- a/luni/src/main/java/java/io/FileInputStream.java
+++ b/luni/src/main/java/java/io/FileInputStream.java
@@ -19,15 +19,13 @@ package java.io;
import dalvik.system.CloseGuard;
-import java.nio.NioUtils;
+import android.system.ErrnoException;
import java.nio.channels.FileChannel;
-import java.util.Arrays;
-import libcore.io.ErrnoException;
+import java.nio.NioUtils;
import libcore.io.IoBridge;
-import libcore.io.IoUtils;
import libcore.io.Libcore;
import libcore.io.Streams;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* An input stream that reads bytes from a file.
@@ -75,7 +73,7 @@ public class FileInputStream extends InputStream {
if (file == null) {
throw new NullPointerException("file == null");
}
- this.fd = IoBridge.open(file.getAbsolutePath(), O_RDONLY);
+ this.fd = IoBridge.open(file.getPath(), O_RDONLY);
this.shouldClose = true;
guard.open("close");
}
@@ -118,7 +116,7 @@ public class FileInputStream extends InputStream {
channel.close();
}
if (shouldClose) {
- IoUtils.close(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
} else {
// An owned fd has been invalidated by IoUtils.close, but
// we need to explicitly stop using an unowned fd (http://b/4361076).
diff --git a/luni/src/main/java/java/io/FileOutputStream.java b/luni/src/main/java/java/io/FileOutputStream.java
index e04ab5f..f91ee20 100644
--- a/luni/src/main/java/java/io/FileOutputStream.java
+++ b/luni/src/main/java/java/io/FileOutputStream.java
@@ -20,10 +20,9 @@ package java.io;
import dalvik.system.CloseGuard;
import java.nio.NioUtils;
import java.nio.channels.FileChannel;
-import java.util.Arrays;
import libcore.io.IoBridge;
-import libcore.io.IoUtils;
-import static libcore.io.OsConstants.*;
+
+import static android.system.OsConstants.*;
/**
* An output stream that writes bytes to a file. If the output file exists, it
@@ -85,7 +84,7 @@ public class FileOutputStream extends OutputStream {
throw new NullPointerException("file == null");
}
this.mode = O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC);
- this.fd = IoBridge.open(file.getAbsolutePath(), mode);
+ this.fd = IoBridge.open(file.getPath(), mode);
this.shouldClose = true;
this.guard.open("close");
}
@@ -136,7 +135,7 @@ public class FileOutputStream extends OutputStream {
channel.close();
}
if (shouldClose) {
- IoUtils.close(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
} else {
// An owned fd has been invalidated by IoUtils.close, but
// we need to explicitly stop using an unowned fd (http://b/4361076).
diff --git a/luni/src/main/java/java/io/InputStream.java b/luni/src/main/java/java/io/InputStream.java
index 973382e..c489b2a 100644
--- a/luni/src/main/java/java/io/InputStream.java
+++ b/luni/src/main/java/java/io/InputStream.java
@@ -209,19 +209,21 @@ public abstract class InputStream extends Object implements Closeable {
}
/**
- * Skips at most {@code n} bytes in this stream. This method does nothing and returns
- * 0 if {@code n} is negative, but some subclasses may throw.
+ * Skips at most {@code byteCount} bytes in this stream. The number of actual
+ * bytes skipped may be anywhere between 0 and {@code byteCount}. If
+ * {@code byteCount} is negative, this method does nothing and returns 0, but
+ * some subclasses may throw.
*
- * <p>Note the "at most" in the description of this method: this method may choose to skip
- * fewer bytes than requested. Callers should <i>always</i> check the return value.
+ * <p>Note the "at most" in the description of this method: this method may
+ * choose to skip fewer bytes than requested. Callers should <i>always</i>
+ * check the return value.
*
- * <p>This default implementation reads bytes into a temporary
- * buffer. Concrete subclasses should provide their own implementation.
+ * <p>This default implementation reads bytes into a temporary buffer. Concrete
+ * subclasses should provide their own implementation.
*
- * @param byteCount the number of bytes to skip.
* @return the number of bytes actually skipped.
- * @throws IOException
- * if this stream is closed or another IOException occurs.
+ * @throws IOException if this stream is closed or another IOException
+ * occurs.
*/
public long skip(long byteCount) throws IOException {
return Streams.skipByReading(this, byteCount);
diff --git a/luni/src/main/java/java/io/InputStreamReader.java b/luni/src/main/java/java/io/InputStreamReader.java
index 01e7f29..d57b916 100644
--- a/luni/src/main/java/java/io/InputStreamReader.java
+++ b/luni/src/main/java/java/io/InputStreamReader.java
@@ -23,8 +23,6 @@ import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
-import java.nio.charset.MalformedInputException;
-import java.nio.charset.UnmappableCharacterException;
import java.util.Arrays;
/**
@@ -260,7 +258,9 @@ public class InputStreamReader extends Reader {
if (result == CoderResult.UNDERFLOW && endOfInput) {
result = decoder.decode(bytes, out, true);
- decoder.flush(out);
+ if (result == CoderResult.UNDERFLOW) {
+ result = decoder.flush(out);
+ }
decoder.reset();
}
if (result.isMalformed() || result.isUnmappable()) {
diff --git a/luni/src/main/java/java/io/ObjectInputStream.java b/luni/src/main/java/java/io/ObjectInputStream.java
index 17a6974..3a89b52 100644
--- a/luni/src/main/java/java/io/ObjectInputStream.java
+++ b/luni/src/main/java/java/io/ObjectInputStream.java
@@ -1739,9 +1739,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec
*/
protected Class<?> resolveProxyClass(String[] interfaceNames)
throws IOException, ClassNotFoundException {
- // TODO: This method is opportunity for performance enhancement
- // We can cache the classloader and recently used interfaces.
- ClassLoader loader = ClassLoader.getSystemClassLoader();
+ ClassLoader loader = callerClassLoader;
Class<?>[] interfaces = new Class<?>[interfaceNames.length];
for (int i = 0; i < interfaceNames.length; i++) {
interfaces[i] = Class.forName(interfaceNames[i], false, loader);
diff --git a/luni/src/main/java/java/io/ObjectOutput.java b/luni/src/main/java/java/io/ObjectOutput.java
index 2e454ec..e6d7b0c 100644
--- a/luni/src/main/java/java/io/ObjectOutput.java
+++ b/luni/src/main/java/java/io/ObjectOutput.java
@@ -18,7 +18,7 @@
package java.io;
/**
- * Defines an interface for classes that allow reading serialized objects.
+ * Defines an interface for classes that allow writing serialized objects.
*
* @see ObjectOutputStream
* @see ObjectInput
diff --git a/luni/src/main/java/java/io/ObjectOutputStream.java b/luni/src/main/java/java/io/ObjectOutputStream.java
index 6a2fbed..f67919d 100644
--- a/luni/src/main/java/java/io/ObjectOutputStream.java
+++ b/luni/src/main/java/java/io/ObjectOutputStream.java
@@ -39,15 +39,13 @@ import libcore.io.SizeOf;
*/
public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants {
- private static final Class<?>[] WRITE_UNSHARED_PARAM_TYPES = new Class[] { Object.class };
-
/*
* Mask to zero SC_BLOC_DATA bit.
*/
private static final byte NOT_SC_BLOCK_DATA = (byte) (SC_BLOCK_DATA ^ 0xFF);
/*
- * How many nested levels to writeObject. We may not need this.
+ * How many nested levels to writeObject.
*/
private int nestedLevels;
@@ -98,11 +96,6 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob
private int protocolVersion;
/*
- * Used to detect nested exception when saving an exception due to an error
- */
- private StreamCorruptedException nestedException;
-
- /*
* Used to keep track of the PutField object for the class/object being
* written
*/
@@ -271,7 +264,6 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob
this.subclassOverridingImplementation = false;
resetState();
- this.nestedException = new StreamCorruptedException();
// So write...() methods can be used by
// subclasses during writeStreamHeader()
primitiveTypes = this.output;
@@ -462,20 +454,6 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob
output.flush();
}
- /*
- * These methods get the value of a field named fieldName of object
- * instance. The field is declared by declaringClass. The field is the same
- * type as the method return value.
- *
- * these methods could be implemented non-natively on top of
- * java.lang.reflect at the expense of extra object creation
- * (java.lang.reflect.Field). Otherwise Serialization could not fetch
- * private fields, except by the use of a native method like this one.
- *
- * @throws NoSuchFieldError If the field does not exist.
- */
- private static native Object getFieldL(Object instance, Class<?> declaringClass, String fieldName, String fieldTypeName);
-
/**
* Return the next handle to be used to indicate cyclic
* references being saved to the stream.
@@ -1236,7 +1214,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob
// The handle for the classDesc is NOT the handle for the class object
// being dumped. We must allocate a new handle and return it.
if (clDesc.isEnum()) {
- writeEnumDesc(object, clDesc, unshared);
+ writeEnumDesc(clDesc, unshared);
} else {
writeClassDesc(clDesc, unshared);
}
@@ -1521,14 +1499,15 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob
primitiveTypes = output;
}
} catch (IOException ioEx1) {
- // This will make it pass through until the top caller. It also
- // lets it pass through the nested exception.
- if (nestedLevels == 0 && ioEx1 != nestedException) {
+ // This will make it pass through until the top caller. Only the top caller writes the
+ // exception (where it can).
+ if (nestedLevels == 0) {
try {
writeNewException(ioEx1);
} catch (IOException ioEx2) {
- nestedException.fillInStackTrace();
- throw nestedException;
+ // If writing the exception to the output stream causes another exception there
+ // is no need to propagate the second exception or generate a third exception,
+ // both of which might obscure details of the root cause.
}
}
throw ioEx1; // and then we propagate the original exception
@@ -1563,9 +1542,8 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob
writeNull();
return -1;
}
- int handle = -1;
if (!unshared) {
- handle = dumpCycle(object);
+ int handle = dumpCycle(object);
if (handle != -1) {
return handle; // cyclic reference
}
@@ -1592,7 +1570,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob
if (clDesc.isSerializable() && computeClassBasedReplacement) {
if (clDesc.hasMethodWriteReplace()){
Method methodWriteReplace = clDesc.getMethodWriteReplace();
- Object replObj = null;
+ Object replObj;
try {
replObj = methodWriteReplace.invoke(object, (Object[]) null);
} catch (IllegalAccessException iae) {
@@ -1677,7 +1655,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob
}
// write for Enum Class Desc only, which is different from other classes
- private ObjectStreamClass writeEnumDesc(Class<?> theClass, ObjectStreamClass classDesc, boolean unshared)
+ private ObjectStreamClass writeEnumDesc(ObjectStreamClass classDesc, boolean unshared)
throws IOException {
// write classDesc, classDesc for enum is different
@@ -1716,7 +1694,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob
if (superClassDesc != null) {
// super class is also enum
superClassDesc.setFlags((byte) (SC_SERIALIZABLE | SC_ENUM));
- writeEnumDesc(superClassDesc.forClass(), superClassDesc, unshared);
+ writeEnumDesc(superClassDesc, unshared);
} else {
output.writeByte(TC_NULL);
}
@@ -1740,7 +1718,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob
theClass = theClass.getSuperclass();
}
ObjectStreamClass classDesc = ObjectStreamClass.lookup(theClass);
- writeEnumDesc(theClass, classDesc, unshared);
+ writeEnumDesc(classDesc, unshared);
int previousHandle = -1;
if (unshared) {
diff --git a/luni/src/main/java/java/io/OutputStreamWriter.java b/luni/src/main/java/java/io/OutputStreamWriter.java
index d69c87a..bc8710d 100644
--- a/luni/src/main/java/java/io/OutputStreamWriter.java
+++ b/luni/src/main/java/java/io/OutputStreamWriter.java
@@ -122,8 +122,8 @@ public class OutputStreamWriter extends Writer {
}
/**
- * Closes this writer. This implementation flushes the buffer as well as the
- * target stream. The target stream is then closed and the resources for the
+ * Closes this writer. This implementation flushes the buffer but <strong>does not</strong>
+ * flush the target stream. The target stream is then closed and the resources for the
* buffer and converter are released.
*
* <p>Only the first invocation of this method has any effect. Subsequent calls
diff --git a/luni/src/main/java/java/io/PipedInputStream.java b/luni/src/main/java/java/io/PipedInputStream.java
index 2c27695..3e81cb8 100644
--- a/luni/src/main/java/java/io/PipedInputStream.java
+++ b/luni/src/main/java/java/io/PipedInputStream.java
@@ -390,6 +390,7 @@ public class PipedInputStream extends InputStream {
if (lastReader != null && !lastReader.isAlive()) {
throw new IOException("Pipe broken");
}
+
notifyAll();
wait(1000);
}
@@ -402,6 +403,10 @@ public class PipedInputStream extends InputStream {
if (in == -1) {
in = 0;
}
+ if (lastReader != null && !lastReader.isAlive()) {
+ throw new IOException("Pipe broken");
+ }
+
buffer[in++] = (byte) oneByte;
if (in == buffer.length) {
in = 0;
diff --git a/luni/src/main/java/java/io/PipedOutputStream.java b/luni/src/main/java/java/io/PipedOutputStream.java
index 1b139e9..4c431c1 100644
--- a/luni/src/main/java/java/io/PipedOutputStream.java
+++ b/luni/src/main/java/java/io/PipedOutputStream.java
@@ -140,7 +140,7 @@ public class PipedOutputStream extends OutputStream {
* @throws IOException
* if this stream is not connected, if the target stream is
* closed or if the thread reading from the target stream is no
- * longer alive. This case is currently not handled correctly.
+ * longer alive.
*/
@Override
public void write(byte[] buffer, int offset, int count) throws IOException {
diff --git a/luni/src/main/java/java/io/PipedReader.java b/luni/src/main/java/java/io/PipedReader.java
index 908505d..7bf9ed4 100644
--- a/luni/src/main/java/java/io/PipedReader.java
+++ b/luni/src/main/java/java/io/PipedReader.java
@@ -33,7 +33,7 @@ public class PipedReader extends Reader {
private Thread lastWriter;
- private boolean isClosed;
+ boolean isClosed;
/**
* The circular buffer through which data is passed. Data is read from the
diff --git a/luni/src/main/java/java/io/PipedWriter.java b/luni/src/main/java/java/io/PipedWriter.java
index ad8974b..e85f69c 100644
--- a/luni/src/main/java/java/io/PipedWriter.java
+++ b/luni/src/main/java/java/io/PipedWriter.java
@@ -17,8 +17,6 @@
package java.io;
-import java.util.Arrays;
-
/**
* Places information on a communications pipe. When two threads want to pass
* data back and forth, one creates a piped writer and the other creates a piped
@@ -115,6 +113,9 @@ public class PipedWriter extends Writer {
}
synchronized (reader) {
+ if (reader.isClosed) {
+ throw new IOException("Pipe is broken");
+ }
reader.notifyAll();
}
}
diff --git a/luni/src/main/java/java/io/RandomAccessFile.java b/luni/src/main/java/java/io/RandomAccessFile.java
index a60da3e..da99765 100644
--- a/luni/src/main/java/java/io/RandomAccessFile.java
+++ b/luni/src/main/java/java/io/RandomAccessFile.java
@@ -17,19 +17,18 @@
package java.io;
+import android.system.ErrnoException;
import dalvik.system.CloseGuard;
import java.nio.ByteOrder;
-import java.nio.NioUtils;
import java.nio.channels.FileChannel;
import java.nio.charset.ModifiedUtf8;
+import java.nio.NioUtils;
import java.util.Arrays;
-import libcore.io.ErrnoException;
import libcore.io.IoBridge;
-import libcore.io.IoUtils;
import libcore.io.Libcore;
import libcore.io.Memory;
import libcore.io.SizeOf;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* Allows reading from and writing to a file in a random-access manner. This is
@@ -115,7 +114,7 @@ public class RandomAccessFile implements DataInput, DataOutput, Closeable {
throw new IllegalArgumentException("Invalid mode: " + mode);
}
this.mode = flags;
- this.fd = IoBridge.open(file.getAbsolutePath(), flags);
+ this.fd = IoBridge.open(file.getPath(), flags);
// if we are in "rws" mode, attempt to sync file+metadata
if (syncMetadata) {
@@ -163,7 +162,7 @@ public class RandomAccessFile implements DataInput, DataOutput, Closeable {
channel.close();
channel = null;
}
- IoUtils.close(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
}
}
diff --git a/luni/src/main/java/java/lang/AbstractStringBuilder.java b/luni/src/main/java/java/lang/AbstractStringBuilder.java
index c3107f2..4d84078 100644
--- a/luni/src/main/java/java/lang/AbstractStringBuilder.java
+++ b/luni/src/main/java/java/lang/AbstractStringBuilder.java
@@ -17,9 +17,10 @@
package java.lang;
+import libcore.util.EmptyArray;
+
import java.io.InvalidObjectException;
import java.util.Arrays;
-import libcore.util.EmptyArray;
/**
* A modifiable {@link CharSequence sequence of characters} for use in creating
@@ -192,14 +193,8 @@ abstract class AbstractStringBuilder {
}
/**
- * Retrieves the character at the {@code index}.
- *
- * @param index
- * the index of the character to retrieve.
- * @return the char value.
- * @throws IndexOutOfBoundsException
- * if {@code index} is negative or greater than or equal to the
- * current {@link #length()}.
+ * Returns the character at {@code index}.
+ * @throws IndexOutOfBoundsException if {@code index < 0} or {@code index >= length()}.
*/
public char charAt(int index) {
if (index < 0 || index >= count) {
@@ -217,50 +212,46 @@ abstract class AbstractStringBuilder {
}
final void delete0(int start, int end) {
- if (start >= 0) {
- if (end > count) {
- end = count;
- }
- if (end == start) {
- return;
- }
- if (end > start) {
- int length = count - end;
- if (length >= 0) {
- if (!shared) {
- System.arraycopy(value, end, value, start, length);
- } else {
- char[] newData = new char[value.length];
- System.arraycopy(value, 0, newData, 0, start);
- System.arraycopy(value, end, newData, start, length);
- value = newData;
- shared = false;
- }
- }
- count -= end - start;
- return;
- }
+ // NOTE: StringBuilder#delete(int, int) is specified not to throw if
+ // the end index is >= count, as long as it's >= start. This means
+ // we have to clamp it to count here.
+ if (end > count) {
+ end = count;
}
- throw startEndAndLength(start, end);
- }
- final void deleteCharAt0(int index) {
- if (index < 0 || index >= count) {
- throw indexAndLength(index);
+ if (start < 0 || start > count || start > end) {
+ throw startEndAndLength(start, end);
}
- int length = count - index - 1;
- if (length > 0) {
+
+ // NOTE: StringBuilder#delete(int, int) throws only if start > count
+ // (start == count is considered valid, oddly enough). Since 'end' is
+ // already a clamped value, that case is handled here.
+ if (end == start) {
+ return;
+ }
+
+ // At this point we know for sure that end > start.
+ int length = count - end;
+ if (length >= 0) {
if (!shared) {
- System.arraycopy(value, index + 1, value, index, length);
+ System.arraycopy(value, end, value, start, length);
} else {
char[] newData = new char[value.length];
- System.arraycopy(value, 0, newData, 0, index);
- System.arraycopy(value, index + 1, newData, index, length);
+ System.arraycopy(value, 0, newData, 0, start);
+ System.arraycopy(value, end, newData, start, length);
value = newData;
shared = false;
}
}
- count--;
+ count -= end - start;
+ }
+
+ final void deleteCharAt0(int index) {
+ if (index < 0 || index >= count) {
+ throw indexAndLength(index);
+ }
+
+ delete0(index, index + 1);
}
/**
@@ -552,7 +543,7 @@ abstract class AbstractStringBuilder {
*
* @param length
* the new length of this StringBuffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code length < 0}.
* @see #length
*/
diff --git a/luni/src/main/java/java/lang/CaseMapper.java b/luni/src/main/java/java/lang/CaseMapper.java
index 5ec41f5..1da621c 100644
--- a/luni/src/main/java/java/lang/CaseMapper.java
+++ b/luni/src/main/java/java/lang/CaseMapper.java
@@ -18,6 +18,7 @@ package java.lang;
import java.util.Locale;
import libcore.icu.ICU;
+import libcore.icu.Transliterator;
/**
* Performs case operations as described by http://unicode.org/reports/tr21/tr21-5.html.
@@ -45,9 +46,10 @@ class CaseMapper {
*/
public static String toLowerCase(Locale locale, String s, char[] value, int offset, int count) {
// Punt hard cases to ICU4C.
+ // Note that Greek isn't a particularly hard case for toLowerCase, only toUpperCase.
String languageCode = locale.getLanguage();
if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) {
- return ICU.toLowerCase(s, locale.toString());
+ return ICU.toLowerCase(s, locale);
}
char[] newValue = null;
@@ -57,7 +59,7 @@ class CaseMapper {
char newCh;
if (ch == LATIN_CAPITAL_I_WITH_DOT || Character.isHighSurrogate(ch)) {
// Punt these hard cases.
- return ICU.toLowerCase(s, locale.toString());
+ return ICU.toLowerCase(s, locale);
} else if (ch == GREEK_CAPITAL_SIGMA && isFinalSigma(value, offset, count, i)) {
newCh = GREEK_SMALL_FINAL_SIGMA;
} else {
@@ -139,10 +141,19 @@ class CaseMapper {
return index;
}
+ private static final ThreadLocal<Transliterator> EL_UPPER = new ThreadLocal<Transliterator>() {
+ @Override protected Transliterator initialValue() {
+ return new Transliterator("el-Upper");
+ }
+ };
+
public static String toUpperCase(Locale locale, String s, char[] value, int offset, int count) {
String languageCode = locale.getLanguage();
if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) {
- return ICU.toUpperCase(s, locale.toString());
+ return ICU.toUpperCase(s, locale);
+ }
+ if (languageCode.equals("el")) {
+ return EL_UPPER.get().transliterate(s);
}
char[] output = null;
@@ -150,7 +161,7 @@ class CaseMapper {
for (int o = offset, end = offset + count; o < end; o++) {
char ch = value[o];
if (Character.isHighSurrogate(ch)) {
- return ICU.toUpperCase(s, locale.toString());
+ return ICU.toUpperCase(s, locale);
}
int index = upperIndex(ch);
if (index == -1) {
diff --git a/luni/src/main/java/java/lang/CharSequence.java b/luni/src/main/java/java/lang/CharSequence.java
index fc1ecd3..0e1ea3f 100644
--- a/luni/src/main/java/java/lang/CharSequence.java
+++ b/luni/src/main/java/java/lang/CharSequence.java
@@ -32,15 +32,8 @@ public interface CharSequence {
public int length();
/**
- * Returns the character at the specified index, with the first character
- * having index zero.
- *
- * @param index
- * the index of the character to return.
- * @return the requested character.
- * @throws IndexOutOfBoundsException
- * if {@code index < 0} or {@code index} is greater than the
- * length of this sequence.
+ * Returns the character at {@code index}.
+ * @throws IndexOutOfBoundsException if {@code index < 0} or {@code index >= length()}.
*/
public char charAt(int index);
diff --git a/luni/src/main/java/java/lang/Character.java b/luni/src/main/java/java/lang/Character.java
index 5762bd4..2046ba8 100644
--- a/luni/src/main/java/java/lang/Character.java
+++ b/luni/src/main/java/java/lang/Character.java
@@ -46,7 +46,7 @@ import java.util.Arrays;
* point or a UTF-16 unit that's part of a surrogate pair. The {@code int} type
* is used to represent all Unicode code points.
*
- * <a name="unicode_categories"><h3>Unicode categories</h3></a>
+ * <a name="unicode_categories"></a><h3>Unicode categories</h3>
* <p>Here's a list of the Unicode character categories and the corresponding Java constant,
* grouped semantically to provide a convenient overview. This table is also useful in
* conjunction with {@code \p} and {@code \P} in {@link java.util.regex.Pattern regular expressions}.
@@ -1489,7 +1489,7 @@ public final class Character implements Serializable, Comparable<Character> {
if (blockName == null) {
throw new NullPointerException("blockName == null");
}
- int block = forNameImpl(blockName);
+ int block = unicodeBlockForName(blockName);
if (block == -1) {
throw new IllegalArgumentException("Unknown block: " + blockName);
}
@@ -1510,7 +1510,7 @@ public final class Character implements Serializable, Comparable<Character> {
*/
public static UnicodeBlock of(int codePoint) {
checkValidCodePoint(codePoint);
- int block = ofImpl(codePoint);
+ int block = unicodeBlockForCodePoint(codePoint);
if (block == -1 || block >= BLOCKS.length) {
return null;
}
@@ -1522,9 +1522,14 @@ public final class Character implements Serializable, Comparable<Character> {
}
}
- private static native int forNameImpl(String blockName);
+ private static native int unicodeBlockForName(String blockName);
+
+ private static native int unicodeBlockForCodePoint(int codePoint);
+
+ private static native int unicodeScriptForName(String blockName);
+
+ private static native int unicodeScriptForCodePoint(int codePoint);
- private static native int ofImpl(int codePoint);
/**
* Constructs a new {@code Character} with the specified primitive char
@@ -2525,25 +2530,28 @@ public final class Character implements Serializable, Comparable<Character> {
}
/**
- * Gets the Unicode directionality of the specified character.
- *
- * @param codePoint
- * the Unicode code point to get the directionality of.
- * @return the Unicode directionality of {@code codePoint}.
+ * Returns the Unicode directionality of the given code point.
+ * This will be one of the {@code DIRECTIONALITY_} constants.
+ * For characters whose directionality is undefined, or whose
+ * directionality has no appropriate constant in this class,
+ * {@code DIRECTIONALITY_UNDEFINED} is returned.
*/
public static byte getDirectionality(int codePoint) {
if (getType(codePoint) == Character.UNASSIGNED) {
return Character.DIRECTIONALITY_UNDEFINED;
}
- byte directionality = getDirectionalityImpl(codePoint);
- if (directionality == -1) {
- return -1;
+ byte directionality = getIcuDirectionality(codePoint);
+ if (directionality >= 0 && directionality < DIRECTIONALITY.length) {
+ return DIRECTIONALITY[directionality];
}
- return DIRECTIONALITY[directionality];
+ return Character.DIRECTIONALITY_UNDEFINED;
}
- private static native byte getDirectionalityImpl(int codePoint);
+ /**
+ * @hide - internal use only.
+ */
+ public static native byte getIcuDirectionality(int codePoint);
/**
* Indicates whether the specified character is mirrored.
diff --git a/luni/src/main/java/java/lang/Double.java b/luni/src/main/java/java/lang/Double.java
index 456529b..cb8c301 100644
--- a/luni/src/main/java/java/lang/Double.java
+++ b/luni/src/main/java/java/lang/Double.java
@@ -171,7 +171,13 @@ public final class Double extends Number implements Comparable<Double> {
* {@code value}. All <em>Not-a-Number (NaN)</em> values are converted to a single NaN
* representation ({@code 0x7ff8000000000000L}) (compare to {@link #doubleToRawLongBits}).
*/
- public static native long doubleToLongBits(double value);
+ public static long doubleToLongBits(double value) {
+ if (value != value) {
+ return 0x7ff8000000000000L; // NaN.
+ } else {
+ return doubleToRawLongBits(value);
+ }
+ }
/**
* Returns an integer corresponding to the bits of the given
diff --git a/luni/src/main/java/java/lang/Float.java b/luni/src/main/java/java/lang/Float.java
index 900b2a0..5f316f1 100644
--- a/luni/src/main/java/java/lang/Float.java
+++ b/luni/src/main/java/java/lang/Float.java
@@ -199,7 +199,13 @@ public final class Float extends Number implements Comparable<Float> {
* float {@code value}. All <em>Not-a-Number (NaN)</em> values are converted to a single NaN
* representation ({@code 0x7fc00000}) (compare to {@link #floatToRawIntBits}).
*/
- public static native int floatToIntBits(float value);
+ public static int floatToIntBits(float value) {
+ if (value != value) {
+ return 0x7fc00000; // NaN.
+ } else {
+ return floatToRawIntBits(value);
+ }
+ }
/**
* Returns an integer corresponding to the bits of the given
diff --git a/luni/src/main/java/java/lang/Integer.java b/luni/src/main/java/java/lang/Integer.java
index fc38b41..8ae0312 100644
--- a/luni/src/main/java/java/lang/Integer.java
+++ b/luni/src/main/java/java/lang/Integer.java
@@ -126,7 +126,8 @@ public final class Integer extends Number implements Comparable<Integer> {
/**
* Compares two {@code int} values.
- * @return 0 if lhs = rhs, less than 0 if lhs &lt; rhs, and greater than 0 if lhs &gt; rhs.
+ * @return 0 if lhs = rhs, less than 0 if lhs &lt; rhs, and greater than 0
+ * if lhs &gt; rhs.
* @since 1.7
*/
public static int compare(int lhs, int rhs) {
@@ -140,24 +141,26 @@ public final class Integer extends Number implements Comparable<Integer> {
/**
* Parses the specified string and returns a {@code Integer} instance if the
* string can be decoded into an integer value. The string may be an
- * optional minus sign "-" followed by a hexadecimal ("0x..." or "#..."),
- * octal ("0..."), or decimal ("...") representation of an integer.
+ * optional sign character ("-" or "+") followed by a hexadecimal ("0x..."
+ * or "#..."), octal ("0..."), or decimal ("...") representation of an
+ * integer.
*
* @param string
* a string representation of an integer value.
* @return an {@code Integer} containing the value represented by
* {@code string}.
* @throws NumberFormatException
- * if {@code string} cannot be parsed as an integer value.
+ * if {@code string} cannot be parsed as an integer value.
*/
public static Integer decode(String string) throws NumberFormatException {
- int length = string.length(), i = 0;
+ int length = string.length();
if (length == 0) {
throw invalidInt(string);
}
+ int i = 0;
char firstDigit = string.charAt(i);
boolean negative = firstDigit == '-';
- if (negative) {
+ if (negative || firstDigit == '+') {
if (length == 1) {
throw invalidInt(string);
}
@@ -319,8 +322,8 @@ public final class Integer extends Number implements Comparable<Integer> {
/**
* Parses the specified string as a signed decimal integer value. The ASCII
- * character \u002d ('-') is recognized as the minus sign.
- *
+ * characters \u002d ('-') and \u002b ('+') are recognized as the minus and
+ * plus signs.
* @param string
* the string representation of an integer value.
* @return the primitive integer value represented by {@code string}.
@@ -333,7 +336,8 @@ public final class Integer extends Number implements Comparable<Integer> {
/**
* Parses the specified string as a signed integer value using the specified
- * radix. The ASCII character \u002d ('-') is recognized as the minus sign.
+ * radix. The ASCII characters \u002d ('-') and \u002b ('+') are recognized
+ * as the minus and plus signs.
*
* @param string
* the string representation of an integer value.
@@ -350,24 +354,56 @@ public final class Integer extends Number implements Comparable<Integer> {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
throw new NumberFormatException("Invalid radix: " + radix);
}
- if (string == null) {
+ if (string == null || string.isEmpty()) {
throw invalidInt(string);
}
- int length = string.length(), i = 0;
- if (length == 0) {
+
+ char firstChar = string.charAt(0);
+ int firstDigitIndex = (firstChar == '-' || firstChar == '+') ? 1 : 0;
+ if (firstDigitIndex == string.length()) {
throw invalidInt(string);
}
- boolean negative = string.charAt(i) == '-';
- if (negative && ++i == length) {
+
+ return parse(string, firstDigitIndex, radix, firstChar == '-');
+ }
+
+ /**
+ * Equivalent to {@code parsePositiveInt(string, 10)}.
+ *
+ * @see #parsePositiveInt(String, int)
+ *
+ * @hide
+ */
+ public static int parsePositiveInt(String string) throws NumberFormatException {
+ return parsePositiveInt(string, 10);
+ }
+
+ /**
+ * Parses the specified string as a positive integer value using the
+ * specified radix. 0 is considered a positive integer.
+ * <p>
+ * This method behaves the same as {@link #parseInt(String, int)} except
+ * that it disallows leading '+' and '-' characters. See that method for
+ * error conditions.
+ *
+ * @see #parseInt(String, int)
+ *
+ * @hide
+ */
+ public static int parsePositiveInt(String string, int radix) throws NumberFormatException {
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ throw new NumberFormatException("Invalid radix: " + radix);
+ }
+ if (string == null || string.length() == 0) {
throw invalidInt(string);
}
-
- return parse(string, i, radix, negative);
+ return parse(string, 0, radix, false);
}
private static int parse(String string, int offset, int radix, boolean negative) throws NumberFormatException {
int max = Integer.MIN_VALUE / radix;
- int result = 0, length = string.length();
+ int result = 0;
+ int length = string.length();
while (offset < length) {
int digit = Character.digit(string.charAt(offset++), radix);
if (digit == -1) {
diff --git a/luni/src/main/java/java/lang/Long.java b/luni/src/main/java/java/lang/Long.java
index 84169af..5c11564 100644
--- a/luni/src/main/java/java/lang/Long.java
+++ b/luni/src/main/java/java/lang/Long.java
@@ -127,8 +127,8 @@ public final class Long extends Number implements Comparable<Long> {
/**
* Parses the specified string and returns a {@code Long} instance if the
* string can be decoded into a long value. The string may be an optional
- * minus sign "-" followed by a hexadecimal ("0x..." or "#..."), octal
- * ("0..."), or decimal ("...") representation of a long.
+ * optional sign character ("-" or "+") followed by a hexadecimal ("0x..."
+ * or "#..."), octal ("0..."), or decimal ("...") representation of a long.
*
* @param string
* a string representation of a long value.
@@ -137,13 +137,15 @@ public final class Long extends Number implements Comparable<Long> {
* if {@code string} cannot be parsed as a long value.
*/
public static Long decode(String string) throws NumberFormatException {
- int length = string.length(), i = 0;
+ int length = string.length();
if (length == 0) {
throw invalidLong(string);
}
+
+ int i = 0;
char firstDigit = string.charAt(i);
boolean negative = firstDigit == '-';
- if (negative) {
+ if (negative || firstDigit == '+') {
if (length == 1) {
throw invalidLong(string);
}
@@ -306,7 +308,8 @@ public final class Long extends Number implements Comparable<Long> {
/**
* Parses the specified string as a signed decimal long value. The ASCII
- * character \u002d ('-') is recognized as the minus sign.
+ * characters \u002d ('-') and \u002b ('+') are recognized as the minus and
+ * plus signs.
*
* @param string
* the string representation of a long value.
@@ -320,7 +323,8 @@ public final class Long extends Number implements Comparable<Long> {
/**
* Parses the specified string as a signed long value using the specified
- * radix. The ASCII character \u002d ('-') is recognized as the minus sign.
+ * radix. The ASCII characters \u002d ('-') and \u002b ('+') are recognized
+ * as the minus and plus signs.
*
* @param string
* the string representation of a long value.
@@ -337,24 +341,22 @@ public final class Long extends Number implements Comparable<Long> {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
throw new NumberFormatException("Invalid radix: " + radix);
}
- if (string == null) {
- throw invalidLong(string);
- }
- int length = string.length(), i = 0;
- if (length == 0) {
+ if (string == null || string.isEmpty()) {
throw invalidLong(string);
}
- boolean negative = string.charAt(i) == '-';
- if (negative && ++i == length) {
+ char firstChar = string.charAt(0);
+ int firstDigitIndex = (firstChar == '-' || firstChar == '+') ? 1 : 0;
+ if (firstDigitIndex == string.length()) {
throw invalidLong(string);
}
- return parse(string, i, radix, negative);
+ return parse(string, firstDigitIndex, radix, firstChar == '-');
}
private static long parse(String string, int offset, int radix, boolean negative) {
long max = Long.MIN_VALUE / radix;
- long result = 0, length = string.length();
+ long result = 0;
+ int length = string.length();
while (offset < length) {
int digit = Character.digit(string.charAt(offset++), radix);
if (digit == -1) {
@@ -378,6 +380,39 @@ public final class Long extends Number implements Comparable<Long> {
return result;
}
+ /**
+ * Equivalent to {@code parsePositiveLong(string, 10)}.
+ *
+ * @see #parsePositiveLong(String, int)
+ *
+ * @hide
+ */
+ public static long parsePositiveLong(String string) throws NumberFormatException {
+ return parsePositiveLong(string, 10);
+ }
+
+ /**
+ * Parses the specified string as a positive long value using the
+ * specified radix. 0 is considered a positive long.
+ * <p>
+ * This method behaves the same as {@link #parseLong(String, int)} except
+ * that it disallows leading '+' and '-' characters. See that method for
+ * error conditions.
+ *
+ * @see #parseLong(String, int)
+ *
+ * @hide
+ */
+ public static long parsePositiveLong(String string, int radix) throws NumberFormatException {
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ throw new NumberFormatException("Invalid radix: " + radix);
+ }
+ if (string == null || string.length() == 0) {
+ throw invalidLong(string);
+ }
+ return parse(string, 0, radix, false);
+ }
+
@Override
public short shortValue() {
return (short) value;
diff --git a/luni/src/main/java/java/lang/ProcessManager.java b/luni/src/main/java/java/lang/ProcessManager.java
index 28314b7..ec87fda 100644
--- a/luni/src/main/java/java/lang/ProcessManager.java
+++ b/luni/src/main/java/java/lang/ProcessManager.java
@@ -16,23 +16,23 @@
package java.lang;
+import android.system.ErrnoException;
+import android.util.MutableInt;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.InputStream;
+import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
import libcore.io.Libcore;
-import libcore.util.MutableInt;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* Manages child processes.
diff --git a/luni/src/main/java/java/lang/Runtime.java b/luni/src/main/java/java/lang/Runtime.java
index 8538f8a..a3cb83e 100644
--- a/luni/src/main/java/java/lang/Runtime.java
+++ b/luni/src/main/java/java/lang/Runtime.java
@@ -48,7 +48,7 @@ import java.util.StringTokenizer;
import libcore.io.IoUtils;
import libcore.io.Libcore;
import libcore.util.EmptyArray;
-import static libcore.io.OsConstants._SC_NPROCESSORS_CONF;
+import static android.system.OsConstants._SC_NPROCESSORS_CONF;
/**
* Allows Java applications to interface with the environment in which they are
@@ -307,57 +307,64 @@ public class Runtime {
}
/**
- * Loads and links the dynamic library that is identified through the
- * specified path. This method is similar to {@link #loadLibrary(String)},
- * but it accepts a full path specification whereas {@code loadLibrary} just
- * accepts the name of the library to load.
+ * Loads the shared library found at the given absolute path.
+ * This should be of the form {@code /path/to/library/libMyLibrary.so}.
+ * Most callers should use {@link #loadLibrary(String)} instead, and
+ * let the system find the correct file to load.
*
- * @param pathName
- * the absolute (platform dependent) path to the library to load.
- * @throws UnsatisfiedLinkError
- * if the library can not be loaded.
+ * @throws UnsatisfiedLinkError if the library can not be loaded,
+ * either because it's not found or because there is something wrong with it.
*/
- public void load(String pathName) {
- load(pathName, VMStack.getCallingClassLoader());
+ public void load(String absolutePath) {
+ load(absolutePath, VMStack.getCallingClassLoader());
}
/*
- * Loads and links the given library without security checks.
+ * Loads the given shared library using the given ClassLoader.
*/
- void load(String pathName, ClassLoader loader) {
- if (pathName == null) {
- throw new NullPointerException("pathName == null");
+ void load(String absolutePath, ClassLoader loader) {
+ if (absolutePath == null) {
+ throw new NullPointerException("absolutePath == null");
}
- String error = doLoad(pathName, loader);
+ String error = doLoad(absolutePath, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}
/**
- * Loads and links the library with the specified name. The mapping of the
- * specified library name to the full path for loading the library is
- * implementation-dependent.
+ * Loads a shared library. Class loaders have some influence over this
+ * process, but for a typical Android app, it works as follows:
*
- * @param libName
- * the name of the library to load.
- * @throws UnsatisfiedLinkError
- * if the library can not be loaded.
+ * <p>Given the name {@code "MyLibrary"}, that string will be passed to
+ * {@link System#mapLibraryName}. That means it would be a mistake
+ * for the caller to include the usual {@code "lib"} prefix and {@code ".so"}
+ * suffix.
+ *
+ * <p>That file will then be searched for on the application's native library
+ * search path. This consists of the application's own native library directory
+ * followed by the system's native library directories.
+ *
+ * @throws UnsatisfiedLinkError if the library can not be loaded,
+ * either because it's not found or because there is something wrong with it.
*/
- public void loadLibrary(String libName) {
- loadLibrary(libName, VMStack.getCallingClassLoader());
+ public void loadLibrary(String nickname) {
+ loadLibrary(nickname, VMStack.getCallingClassLoader());
}
/*
- * Searches for a library, then loads and links it without security checks.
+ * Searches for and loads the given shared library using the given ClassLoader.
*/
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
- throw new UnsatisfiedLinkError("Couldn't load " + libraryName +
- " from loader " + loader +
- ": findLibrary returned null");
+ // It's not necessarily true that the ClassLoader used
+ // System.mapLibraryName, but the default setup does, and it's
+ // misleading to say we didn't find "libMyLibrary.so" when we
+ // actually searched for "liblibMyLibrary.so.so".
+ throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
+ System.mapLibraryName(libraryName) + "\"");
}
String error = doLoad(filename, loader);
if (error != null) {
diff --git a/luni/src/main/java/java/lang/StringToReal.java b/luni/src/main/java/java/lang/StringToReal.java
index 97f6d6b..4bd4fb1 100644
--- a/luni/src/main/java/java/lang/StringToReal.java
+++ b/luni/src/main/java/java/lang/StringToReal.java
@@ -122,20 +122,20 @@ final class StringToReal {
result.e = -result.e;
}
} catch (NumberFormatException ex) {
- // We already checked the string, so the exponent must have been out of range for an int.
+ // We already checked the string, so the exponent must have been out of range for an
+ // int.
if (negativeExponent) {
result.zero = true;
} else {
result.infinity = true;
}
- return result;
+ // Fall through: We want to check the content of the mantissa and throw an
+ // exception if it contains invalid characters. For example: "JUNK" * 10^12 should
+ // be treated as an error, not as infinity.
}
} else {
end = length;
}
- if (length == 0) {
- throw invalidReal(s, isDouble);
- }
int start = 0;
c = s.charAt(start);
@@ -151,7 +151,19 @@ final class StringToReal {
throw invalidReal(s, isDouble);
}
- int decimal = s.indexOf('.');
+ // Confirm that the mantissa should parse.
+ int decimal = -1;
+ for (int i = start; i < end; i++) {
+ char mc = s.charAt(i);
+ if (mc == '.') {
+ if (decimal != -1) {
+ throw invalidReal(s, isDouble);
+ }
+ decimal = i;
+ } else if (mc < '0' || mc > '9') {
+ throw invalidReal(s, isDouble);
+ }
+ }
if (decimal > -1) {
result.e -= end - decimal - 1;
s = s.substring(start, decimal) + s.substring(decimal + 1, end);
@@ -159,10 +171,17 @@ final class StringToReal {
s = s.substring(start, end);
}
- if ((length = s.length()) == 0) {
+ length = s.length();
+ if (length == 0) {
throw invalidReal(s, isDouble);
}
+ // All syntactic checks that might throw an exception are above. If we have established
+ // one of the non-exception error conditions we can stop here.
+ if (result.infinity || result.zero) {
+ return result;
+ }
+
end = length;
while (end > 1 && s.charAt(end - 1) == '0') {
--end;
@@ -237,7 +256,7 @@ final class StringToReal {
* the String that will be parsed to a floating point
* @return the double closest to the real number
*
- * @exception NumberFormatException
+ * @throws NumberFormatException
* if the String doesn't represent a double
*/
public static double parseDouble(String s) {
@@ -278,7 +297,7 @@ final class StringToReal {
* the String that will be parsed to a floating point
* @return the float closest to the real number
*
- * @exception NumberFormatException
+ * @throws NumberFormatException
* if the String doesn't represent a float
*/
public static float parseFloat(String s) {
diff --git a/luni/src/main/java/java/lang/System.java b/luni/src/main/java/java/lang/System.java
index 68ca506..55ca762 100644
--- a/luni/src/main/java/java/lang/System.java
+++ b/luni/src/main/java/java/lang/System.java
@@ -32,6 +32,9 @@
package java.lang;
+import android.system.ErrnoException;
+import android.system.StructPasswd;
+import android.system.StructUtsname;
import dalvik.system.VMRuntime;
import dalvik.system.VMStack;
import java.io.BufferedInputStream;
@@ -39,8 +42,8 @@ import java.io.Console;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.InputStream;
+import java.io.IOException;
import java.io.PrintStream;
import java.nio.channels.Channel;
import java.nio.channels.spi.SelectorProvider;
@@ -51,11 +54,7 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import libcore.icu.ICU;
-import libcore.io.ErrnoException;
import libcore.io.Libcore;
-import libcore.io.StructPasswd;
-import libcore.io.StructUtsname;
-import libcore.util.ZoneInfoDB;
/**
* Provides access to system-related information and resources including
@@ -83,12 +82,31 @@ public final class System {
public static final PrintStream err;
private static final String lineSeparator;
+ private static final Properties unchangeableSystemProperties;
private static Properties systemProperties;
+ /**
+ * Dedicated lock for GC / Finalization logic.
+ */
+ private static final Object lock = new Object();
+
+ /**
+ * Whether or not we need to do a GC before running the finalizers.
+ */
+ private static boolean runGC;
+
+ /**
+ * If we just ran finalization, we might want to do a GC to free the finalized objects.
+ * This lets us do gc/runFinlization/gc sequences but prevents back to back System.gc().
+ */
+ private static boolean justRanFinalization;
+
static {
err = new PrintStream(new FileOutputStream(FileDescriptor.err));
out = new PrintStream(new FileOutputStream(FileDescriptor.out));
in = new BufferedInputStream(new FileInputStream(FileDescriptor.in));
+ unchangeableSystemProperties = initUnchangeableSystemProperties();
+ systemProperties = createSystemProperties();
lineSeparator = System.getProperty("line.separator");
}
@@ -153,7 +171,433 @@ public final class System {
* @param length
* the number of elements to be copied.
*/
- public static native void arraycopy(Object src, int srcPos, Object dst, int dstPos, int length);
+
+ public static native void arraycopy(Object src, int srcPos,
+ Object dst, int dstPos, int length);
+
+ /**
+ * The char array length threshold below which to use a Java
+ * (non-native) version of arraycopy() instead of the native
+ * version. See b/7103825.
+ */
+ private static final int ARRAYCOPY_SHORT_CHAR_ARRAY_THRESHOLD = 32;
+
+ /**
+ * The char[] specialized version of arraycopy().
+ *
+ * @hide internal use only
+ */
+ public static void arraycopy(char[] src, int srcPos, char[] dst, int dstPos, int length) {
+ if (src == null) {
+ throw new NullPointerException("src == null");
+ }
+ if (dst == null) {
+ throw new NullPointerException("dst == null");
+ }
+ if (srcPos < 0 || dstPos < 0 || length < 0 ||
+ srcPos > src.length - length || dstPos > dst.length - length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "src.length=" + src.length + " srcPos=" + srcPos +
+ " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length);
+ }
+ if (length <= ARRAYCOPY_SHORT_CHAR_ARRAY_THRESHOLD) {
+ // Copy char by char for shorter arrays.
+ if (src == dst && srcPos < dstPos && dstPos < srcPos + length) {
+ // Copy backward (to avoid overwriting elements before
+ // they are copied in case of an overlap on the same
+ // array.)
+ for (int i = length - 1; i >= 0; --i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ } else {
+ // Copy forward.
+ for (int i = 0; i < length; ++i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ }
+ } else {
+ // Call the native version for longer arrays.
+ arraycopyCharUnchecked(src, srcPos, dst, dstPos, length);
+ }
+ }
+
+ /**
+ * The char[] specialized, unchecked, native version of
+ * arraycopy(). This assumes error checking has been done.
+ */
+ private static native void arraycopyCharUnchecked(char[] src, int srcPos,
+ char[] dst, int dstPos, int length);
+
+ /**
+ * The byte array length threshold below which to use a Java
+ * (non-native) version of arraycopy() instead of the native
+ * version. See b/7103825.
+ */
+ private static final int ARRAYCOPY_SHORT_BYTE_ARRAY_THRESHOLD = 32;
+
+ /**
+ * The byte[] specialized version of arraycopy().
+ *
+ * @hide internal use only
+ */
+ public static void arraycopy(byte[] src, int srcPos, byte[] dst, int dstPos, int length) {
+ if (src == null) {
+ throw new NullPointerException("src == null");
+ }
+ if (dst == null) {
+ throw new NullPointerException("dst == null");
+ }
+ if (srcPos < 0 || dstPos < 0 || length < 0 ||
+ srcPos > src.length - length || dstPos > dst.length - length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "src.length=" + src.length + " srcPos=" + srcPos +
+ " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length);
+ }
+ if (length <= ARRAYCOPY_SHORT_BYTE_ARRAY_THRESHOLD) {
+ // Copy byte by byte for shorter arrays.
+ if (src == dst && srcPos < dstPos && dstPos < srcPos + length) {
+ // Copy backward (to avoid overwriting elements before
+ // they are copied in case of an overlap on the same
+ // array.)
+ for (int i = length - 1; i >= 0; --i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ } else {
+ // Copy forward.
+ for (int i = 0; i < length; ++i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ }
+ } else {
+ // Call the native version for longer arrays.
+ arraycopyByteUnchecked(src, srcPos, dst, dstPos, length);
+ }
+ }
+
+ /**
+ * The byte[] specialized, unchecked, native version of
+ * arraycopy(). This assumes error checking has been done.
+ */
+ private static native void arraycopyByteUnchecked(byte[] src, int srcPos,
+ byte[] dst, int dstPos, int length);
+
+ /**
+ * The short array length threshold below which to use a Java
+ * (non-native) version of arraycopy() instead of the native
+ * version. See b/7103825.
+ */
+ private static final int ARRAYCOPY_SHORT_SHORT_ARRAY_THRESHOLD = 32;
+
+ /**
+ * The short[] specialized version of arraycopy().
+ *
+ * @hide internal use only
+ */
+ public static void arraycopy(short[] src, int srcPos, short[] dst, int dstPos, int length) {
+ if (src == null) {
+ throw new NullPointerException("src == null");
+ }
+ if (dst == null) {
+ throw new NullPointerException("dst == null");
+ }
+ if (srcPos < 0 || dstPos < 0 || length < 0 ||
+ srcPos > src.length - length || dstPos > dst.length - length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "src.length=" + src.length + " srcPos=" + srcPos +
+ " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length);
+ }
+ if (length <= ARRAYCOPY_SHORT_SHORT_ARRAY_THRESHOLD) {
+ // Copy short by short for shorter arrays.
+ if (src == dst && srcPos < dstPos && dstPos < srcPos + length) {
+ // Copy backward (to avoid overwriting elements before
+ // they are copied in case of an overlap on the same
+ // array.)
+ for (int i = length - 1; i >= 0; --i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ } else {
+ // Copy forward.
+ for (int i = 0; i < length; ++i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ }
+ } else {
+ // Call the native version for longer arrays.
+ arraycopyShortUnchecked(src, srcPos, dst, dstPos, length);
+ }
+ }
+
+ /**
+ * The short[] specialized, unchecked, native version of
+ * arraycopy(). This assumes error checking has been done.
+ */
+ private static native void arraycopyShortUnchecked(short[] src, int srcPos,
+ short[] dst, int dstPos, int length);
+
+ /**
+ * The short array length threshold below which to use a Java
+ * (non-native) version of arraycopy() instead of the native
+ * version. See b/7103825.
+ */
+ private static final int ARRAYCOPY_SHORT_INT_ARRAY_THRESHOLD = 32;
+
+ /**
+ * The int[] specialized version of arraycopy().
+ *
+ * @hide internal use only
+ */
+ public static void arraycopy(int[] src, int srcPos, int[] dst, int dstPos, int length) {
+ if (src == null) {
+ throw new NullPointerException("src == null");
+ }
+ if (dst == null) {
+ throw new NullPointerException("dst == null");
+ }
+ if (srcPos < 0 || dstPos < 0 || length < 0 ||
+ srcPos > src.length - length || dstPos > dst.length - length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "src.length=" + src.length + " srcPos=" + srcPos +
+ " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length);
+ }
+ if (length <= ARRAYCOPY_SHORT_INT_ARRAY_THRESHOLD) {
+ // Copy int by int for shorter arrays.
+ if (src == dst && srcPos < dstPos && dstPos < srcPos + length) {
+ // Copy backward (to avoid overwriting elements before
+ // they are copied in case of an overlap on the same
+ // array.)
+ for (int i = length - 1; i >= 0; --i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ } else {
+ // Copy forward.
+ for (int i = 0; i < length; ++i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ }
+ } else {
+ // Call the native version for longer arrays.
+ arraycopyIntUnchecked(src, srcPos, dst, dstPos, length);
+ }
+ }
+
+ /**
+ * The int[] specialized, unchecked, native version of
+ * arraycopy(). This assumes error checking has been done.
+ */
+ private static native void arraycopyIntUnchecked(int[] src, int srcPos,
+ int[] dst, int dstPos, int length);
+
+ /**
+ * The short array length threshold below which to use a Java
+ * (non-native) version of arraycopy() instead of the native
+ * version. See b/7103825.
+ */
+ private static final int ARRAYCOPY_SHORT_LONG_ARRAY_THRESHOLD = 32;
+
+ /**
+ * The long[] specialized version of arraycopy().
+ *
+ * @hide internal use only
+ */
+ public static void arraycopy(long[] src, int srcPos, long[] dst, int dstPos, int length) {
+ if (src == null) {
+ throw new NullPointerException("src == null");
+ }
+ if (dst == null) {
+ throw new NullPointerException("dst == null");
+ }
+ if (srcPos < 0 || dstPos < 0 || length < 0 ||
+ srcPos > src.length - length || dstPos > dst.length - length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "src.length=" + src.length + " srcPos=" + srcPos +
+ " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length);
+ }
+ if (length <= ARRAYCOPY_SHORT_LONG_ARRAY_THRESHOLD) {
+ // Copy long by long for shorter arrays.
+ if (src == dst && srcPos < dstPos && dstPos < srcPos + length) {
+ // Copy backward (to avoid overwriting elements before
+ // they are copied in case of an overlap on the same
+ // array.)
+ for (int i = length - 1; i >= 0; --i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ } else {
+ // Copy forward.
+ for (int i = 0; i < length; ++i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ }
+ } else {
+ // Call the native version for longer arrays.
+ arraycopyLongUnchecked(src, srcPos, dst, dstPos, length);
+ }
+ }
+
+ /**
+ * The long[] specialized, unchecked, native version of
+ * arraycopy(). This assumes error checking has been done.
+ */
+ private static native void arraycopyLongUnchecked(long[] src, int srcPos,
+ long[] dst, int dstPos, int length);
+
+ /**
+ * The short array length threshold below which to use a Java
+ * (non-native) version of arraycopy() instead of the native
+ * version. See b/7103825.
+ */
+ private static final int ARRAYCOPY_SHORT_FLOAT_ARRAY_THRESHOLD = 32;
+
+ /**
+ * The float[] specialized version of arraycopy().
+ *
+ * @hide internal use only
+ */
+ public static void arraycopy(float[] src, int srcPos, float[] dst, int dstPos, int length) {
+ if (src == null) {
+ throw new NullPointerException("src == null");
+ }
+ if (dst == null) {
+ throw new NullPointerException("dst == null");
+ }
+ if (srcPos < 0 || dstPos < 0 || length < 0 ||
+ srcPos > src.length - length || dstPos > dst.length - length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "src.length=" + src.length + " srcPos=" + srcPos +
+ " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length);
+ }
+ if (length <= ARRAYCOPY_SHORT_FLOAT_ARRAY_THRESHOLD) {
+ // Copy float by float for shorter arrays.
+ if (src == dst && srcPos < dstPos && dstPos < srcPos + length) {
+ // Copy backward (to avoid overwriting elements before
+ // they are copied in case of an overlap on the same
+ // array.)
+ for (int i = length - 1; i >= 0; --i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ } else {
+ // Copy forward.
+ for (int i = 0; i < length; ++i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ }
+ } else {
+ // Call the native version for floater arrays.
+ arraycopyFloatUnchecked(src, srcPos, dst, dstPos, length);
+ }
+ }
+
+ /**
+ * The float[] specialized, unchecked, native version of
+ * arraycopy(). This assumes error checking has been done.
+ */
+ private static native void arraycopyFloatUnchecked(float[] src, int srcPos,
+ float[] dst, int dstPos, int length);
+
+ /**
+ * The short array length threshold below which to use a Java
+ * (non-native) version of arraycopy() instead of the native
+ * version. See b/7103825.
+ */
+ private static final int ARRAYCOPY_SHORT_DOUBLE_ARRAY_THRESHOLD = 32;
+
+ /**
+ * The double[] specialized version of arraycopy().
+ *
+ * @hide internal use only
+ */
+ public static void arraycopy(double[] src, int srcPos, double[] dst, int dstPos, int length) {
+ if (src == null) {
+ throw new NullPointerException("src == null");
+ }
+ if (dst == null) {
+ throw new NullPointerException("dst == null");
+ }
+ if (srcPos < 0 || dstPos < 0 || length < 0 ||
+ srcPos > src.length - length || dstPos > dst.length - length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "src.length=" + src.length + " srcPos=" + srcPos +
+ " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length);
+ }
+ if (length <= ARRAYCOPY_SHORT_DOUBLE_ARRAY_THRESHOLD) {
+ // Copy double by double for shorter arrays.
+ if (src == dst && srcPos < dstPos && dstPos < srcPos + length) {
+ // Copy backward (to avoid overwriting elements before
+ // they are copied in case of an overlap on the same
+ // array.)
+ for (int i = length - 1; i >= 0; --i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ } else {
+ // Copy forward.
+ for (int i = 0; i < length; ++i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ }
+ } else {
+ // Call the native version for floater arrays.
+ arraycopyDoubleUnchecked(src, srcPos, dst, dstPos, length);
+ }
+ }
+
+ /**
+ * The double[] specialized, unchecked, native version of
+ * arraycopy(). This assumes error checking has been done.
+ */
+ private static native void arraycopyDoubleUnchecked(double[] src, int srcPos,
+ double[] dst, int dstPos, int length);
+
+ /**
+ * The short array length threshold below which to use a Java
+ * (non-native) version of arraycopy() instead of the native
+ * version. See b/7103825.
+ */
+ private static final int ARRAYCOPY_SHORT_BOOLEAN_ARRAY_THRESHOLD = 32;
+
+ /**
+ * The boolean[] specialized version of arraycopy().
+ *
+ * @hide internal use only
+ */
+ public static void arraycopy(boolean[] src, int srcPos, boolean[] dst, int dstPos, int length) {
+ if (src == null) {
+ throw new NullPointerException("src == null");
+ }
+ if (dst == null) {
+ throw new NullPointerException("dst == null");
+ }
+ if (srcPos < 0 || dstPos < 0 || length < 0 ||
+ srcPos > src.length - length || dstPos > dst.length - length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "src.length=" + src.length + " srcPos=" + srcPos +
+ " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length);
+ }
+ if (length <= ARRAYCOPY_SHORT_BOOLEAN_ARRAY_THRESHOLD) {
+ // Copy boolean by boolean for shorter arrays.
+ if (src == dst && srcPos < dstPos && dstPos < srcPos + length) {
+ // Copy backward (to avoid overwriting elements before
+ // they are copied in case of an overlap on the same
+ // array.)
+ for (int i = length - 1; i >= 0; --i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ } else {
+ // Copy forward.
+ for (int i = 0; i < length; ++i) {
+ dst[dstPos + i] = src[srcPos + i];
+ }
+ }
+ } else {
+ // Call the native version for floater arrays.
+ arraycopyBooleanUnchecked(src, srcPos, dst, dstPos, length);
+ }
+ }
+
+ /**
+ * The boolean[] specialized, unchecked, native version of
+ * arraycopy(). This assumes error checking has been done.
+ */
+ private static native void arraycopyBooleanUnchecked(boolean[] src, int srcPos,
+ boolean[] dst, int dstPos, int length);
/**
* Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC.
@@ -196,7 +640,18 @@ public final class System {
* that the garbage collector will actually be run.
*/
public static void gc() {
- Runtime.getRuntime().gc();
+ boolean shouldRunGC;
+ synchronized(lock) {
+ shouldRunGC = justRanFinalization;
+ if (shouldRunGC) {
+ justRanFinalization = false;
+ } else {
+ runGC = true;
+ }
+ }
+ if (shouldRunGC) {
+ Runtime.getRuntime().gc();
+ }
}
/**
@@ -246,13 +701,10 @@ public final class System {
* @return the system properties.
*/
public static Properties getProperties() {
- if (systemProperties == null) {
- initSystemProperties();
- }
return systemProperties;
}
- private static void initSystemProperties() {
+ private static Properties initUnchangeableSystemProperties() {
VMRuntime runtime = VMRuntime.getRuntime();
Properties p = new Properties();
@@ -277,15 +729,6 @@ public final class System {
}
p.put("java.home", javaHome);
- // On Android, each app gets its own temporary directory. This is just a fallback
- // default, useful only on the host.
- p.put("java.io.tmpdir", "/tmp");
-
- String ldLibraryPath = getenv("LD_LIBRARY_PATH");
- if (ldLibraryPath != null) {
- p.put("java.library.path", ldLibraryPath);
- }
-
p.put("java.specification.name", "Dalvik Core Library");
p.put("java.specification.vendor", projectName);
p.put("java.specification.version", "0.9");
@@ -313,7 +756,6 @@ public final class System {
try {
StructPasswd passwd = Libcore.os.getpwuid(Libcore.os.getuid());
- p.put("user.home", passwd.pw_dir);
p.put("user.name", passwd.pw_name);
} catch (ErrnoException exception) {
throw new AssertionError(exception);
@@ -333,8 +775,36 @@ public final class System {
// Override built-in properties with settings from the command line.
parsePropertyAssignments(p, runtime.properties());
+ return p;
+ }
+
+ /**
+ * Inits an unchangeable system property with the given value.
+ * This is useful when the environment needs to change under native bridge emulation.
+ */
+ private static void initUnchangeableSystemProperty(String name, String value) {
+ checkPropertyName(name);
+ unchangeableSystemProperties.put(name, value);
+ }
+
+ private static void setDefaultChangeableProperties(Properties p) {
+ // On Android, each app gets its own temporary directory.
+ // (See android.app.ActivityThread.) This is just a fallback default,
+ // useful only on the host.
+ p.put("java.io.tmpdir", "/tmp");
- systemProperties = p;
+ // Android has always had an empty "user.home" (see docs for getProperty).
+ // This is not useful for normal android apps which need to use android specific
+ // APIs such as {@code Context.getFilesDir} and {@code Context.getCacheDir} but
+ // we make it changeable for backward compatibility, so that they can change it
+ // to a writeable location if required.
+ p.put("user.home", "");
+ }
+
+ private static Properties createSystemProperties() {
+ Properties p = new PropertiesWithNonOverrideableDefaults(unchangeableSystemProperties);
+ setDefaultChangeableProperties(p);
+ return p;
}
/**
@@ -360,7 +830,7 @@ public final class System {
* Returns the value of a particular system property or {@code null} if no
* such property exists.
*
- * <p>The following properties are always provided by the Dalvik VM:
+ * <p>The following properties are always provided by the Dalvik VM:</p>
* <p><table BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0" SUMMARY="">
* <tr BGCOLOR="#CCCCFF" CLASS="TableHeadingColor">
* <td><b>Name</b></td> <td><b>Meaning</b></td> <td><b>Example</b></td></tr>
@@ -401,7 +871,8 @@ public final class System {
*
* </table>
*
- * <p>It is a mistake to try to override any of these. Doing so will have unpredictable results.
+ * <p> All of the above properties except for {@code user.home} and {@code java.io.tmpdir}
+ * <b>cannot be modified</b>. Any attempt to change them will be a no-op.
*
* @param propertyName
* the name of the system property to look up.
@@ -418,22 +889,26 @@ public final class System {
*/
public static String getProperty(String name, String defaultValue) {
checkPropertyName(name);
- return getProperties().getProperty(name, defaultValue);
+ return systemProperties.getProperty(name, defaultValue);
}
/**
- * Sets the value of a particular system property.
+ * Sets the value of a particular system property. Most system properties
+ * are read only and cannot be cleared or modified. See {@link #setProperty} for a
+ * list of such properties.
*
* @return the old value of the property or {@code null} if the property
* didn't exist.
*/
public static String setProperty(String name, String value) {
checkPropertyName(name);
- return (String) getProperties().setProperty(name, value);
+ return (String) systemProperties.setProperty(name, value);
}
/**
- * Removes a specific system property.
+ * Removes a specific system property. Most system properties
+ * are read only and cannot be cleared or modified. See {@link #setProperty} for a
+ * list of such properties.
*
* @return the property value or {@code null} if the property didn't exist.
* @throws NullPointerException
@@ -443,7 +918,7 @@ public final class System {
*/
public static String clearProperty(String name) {
checkPropertyName(name);
- return (String) getProperties().remove(name);
+ return (String) systemProperties.remove(name);
}
private static void checkPropertyName(String name) {
@@ -500,27 +975,14 @@ public final class System {
}
/**
- * Loads and links the dynamic library that is identified through the
- * specified path. This method is similar to {@link #loadLibrary(String)},
- * but it accepts a full path specification whereas {@code loadLibrary} just
- * accepts the name of the library to load.
- *
- * @param pathName
- * the path of the file to be loaded.
+ * See {@link Runtime#load}.
*/
public static void load(String pathName) {
Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
}
/**
- * Loads and links the library with the specified name. The mapping of the
- * specified library name to the full path for loading the library is
- * implementation-dependent.
- *
- * @param libName
- * the name of the library to load.
- * @throws UnsatisfiedLinkError
- * if the library could not be loaded.
+ * See {@link Runtime#loadLibrary}.
*/
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
@@ -575,7 +1037,18 @@ public final class System {
* to perform any outstanding object finalization.
*/
public static void runFinalization() {
+ boolean shouldRunGC;
+ synchronized(lock) {
+ shouldRunGC = runGC;
+ runGC = false;
+ }
+ if (shouldRunGC) {
+ Runtime.getRuntime().gc();
+ }
Runtime.getRuntime().runFinalization();
+ synchronized(lock) {
+ justRanFinalization = true;
+ }
}
/**
@@ -594,12 +1067,21 @@ public final class System {
}
/**
- * Sets all system properties. This does not take a copy; the passed-in object is used
- * directly. Passing null causes the VM to reinitialize the properties to how they were
- * when the VM was started.
+ * Attempts to set all system properties. Copies all properties from
+ * {@code p} and discards system properties that are read only and cannot
+ * be modified. See {@link #setProperty} for a list of such properties.
*/
public static void setProperties(Properties p) {
- systemProperties = p;
+ PropertiesWithNonOverrideableDefaults userProperties =
+ new PropertiesWithNonOverrideableDefaults(unchangeableSystemProperties);
+ if (p != null) {
+ userProperties.putAll(p);
+ } else {
+ // setProperties(null) is documented to restore defaults.
+ setDefaultChangeableProperties(userProperties);
+ }
+
+ systemProperties = userProperties;
}
/**
@@ -621,25 +1103,46 @@ public final class System {
/**
* Returns the platform specific file name format for the shared library
- * named by the argument.
- *
- * @param userLibName
- * the name of the library to look up.
- * @return the platform specific filename for the library.
+ * named by the argument. On Android, this would turn {@code "MyLibrary"} into
+ * {@code "libMyLibrary.so"}.
*/
- public static native String mapLibraryName(String userLibName);
+ public static native String mapLibraryName(String nickname);
/**
- * Sets the value of the named static field in the receiver to the passed in
- * argument.
- *
- * @param fieldName
- * the name of the field to set, one of in, out, or err
- * @param stream
- * the new value of the field
+ * Used to set System.err, System.in, and System.out.
*/
- private static native void setFieldImpl(String fieldName, String signature, Object stream);
+ private static native void setFieldImpl(String field, String signature, Object stream);
+ /**
+ * A properties class that prohibits changes to any of the properties
+ * contained in its defaults.
+ */
+ static final class PropertiesWithNonOverrideableDefaults extends Properties {
+ PropertiesWithNonOverrideableDefaults(Properties defaults) {
+ super(defaults);
+ }
+
+ @Override
+ public Object put(Object key, Object value) {
+ if (defaults.containsKey(key)) {
+ logE("Ignoring attempt to set property \"" + key +
+ "\" to value \"" + value + "\".");
+ return defaults.get(key);
+ }
+
+ return super.put(key, value);
+ }
+
+ @Override
+ public Object remove(Object key) {
+ if (defaults.containsKey(key)) {
+ logE("Ignoring attempt to remove property \"" + key + "\".");
+ return null;
+ }
+
+ return super.remove(key);
+ }
+ }
/**
* The unmodifiable environment variables map. System.getenv() specifies
diff --git a/luni/src/main/java/java/lang/ref/FinalizerReference.java b/luni/src/main/java/java/lang/ref/FinalizerReference.java
index 14eaae4..5416a80 100644
--- a/luni/src/main/java/java/lang/ref/FinalizerReference.java
+++ b/luni/src/main/java/java/lang/ref/FinalizerReference.java
@@ -83,12 +83,18 @@ public final class FinalizerReference<T> extends Reference<T> {
* Waits for all currently-enqueued references to be finalized.
*/
public static void finalizeAllEnqueued() throws InterruptedException {
- Sentinel sentinel = new Sentinel();
- enqueueSentinelReference(sentinel);
+ // Alloate a new sentinel, this creates a FinalizerReference.
+ Sentinel sentinel;
+ // Keep looping until we safely enqueue our sentinel FinalizerReference.
+ // This is done to prevent races where the GC updates the pendingNext
+ // before we get the chance.
+ do {
+ sentinel = new Sentinel();
+ } while (!enqueueSentinelReference(sentinel));
sentinel.awaitFinalization();
}
- private static void enqueueSentinelReference(Sentinel sentinel) {
+ private static boolean enqueueSentinelReference(Sentinel sentinel) {
synchronized (LIST_LOCK) {
// When a finalizable object is allocated, a FinalizerReference is added to the list.
// We search the list for that FinalizerReference (it should be at or near the head),
@@ -98,8 +104,20 @@ public final class FinalizerReference<T> extends Reference<T> {
FinalizerReference<Sentinel> sentinelReference = (FinalizerReference<Sentinel>) r;
sentinelReference.referent = null;
sentinelReference.zombie = sentinel;
- sentinelReference.enqueueInternal();
- return;
+ // Make a single element list, then enqueue the reference on the daemon unenqueued
+ // list. This is required instead of enqueuing directly on the finalizer queue
+ // since there could be recently freed objects in the unqueued list which are not
+ // yet on the finalizer queue. This could cause the sentinel to run before the
+ // objects are finalized. b/17381967
+ // Make circular list if unenqueued goes through native so that we can prevent
+ // races where the GC updates the pendingNext before we do. If it is non null, then
+ // we update the pending next to make a circular list while holding a lock.
+ // b/17462553
+ if (!sentinelReference.makeCircularListIfUnenqueued()) {
+ return false;
+ }
+ ReferenceQueue.add(sentinelReference);
+ return true;
}
}
}
@@ -108,6 +126,8 @@ public final class FinalizerReference<T> extends Reference<T> {
throw new AssertionError("newly-created live Sentinel not on list!");
}
+ private native boolean makeCircularListIfUnenqueued();
+
/**
* A marker object that we can immediately enqueue. When this object's
* finalize() method is called, we know all previously-enqueued finalizable
diff --git a/luni/src/main/java/java/lang/ref/Reference.java b/luni/src/main/java/java/lang/ref/Reference.java
deleted file mode 100644
index bd63535..0000000
--- a/luni/src/main/java/java/lang/ref/Reference.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.lang.ref;
-
-/**
- * Provides an abstract class which describes behavior common to all reference
- * objects. It is not possible to create immediate subclasses of
- * {@code Reference} in addition to the ones provided by this package. It is
- * also not desirable to do so, since references require very close cooperation
- * with the system's garbage collector. The existing, specialized reference
- * classes should be used instead.
- *
- * <p>Three different type of references exist, each being weaker than the preceding one:
- * {@link java.lang.ref.SoftReference}, {@link java.lang.ref.WeakReference}, and
- * {@link java.lang.ref.PhantomReference}. "Weakness" here means that less restrictions are
- * being imposed on the garbage collector as to when it is allowed to
- * actually garbage-collect the referenced object.
- *
- * <p>In order to use reference objects properly it is important to understand
- * the different types of reachability that trigger their clearing and
- * enqueueing. The following table lists these, from strongest to weakest.
- * For each row, an object is said to have the reachability on the left side
- * if (and only if) it fulfills all of the requirements on the right side. In
- * all rows, consider the <em>root set</em> to be a set of references that
- * are "resistant" to garbage collection (that is, running threads, method
- * parameters, local variables, static fields and the like).
- *
- * <p><table>
- * <tr>
- * <td>Strongly reachable</td>
- * <td> <ul>
- * <li>There exists at least one path from the root set to the object that does not traverse any
- * instance of a {@code java.lang.ref.Reference} subclass.
- * </li>
- * </ul> </td>
- * </tr>
- *
- * <tr>
- * <td>Softly reachable</td>
- * <td> <ul>
- * <li>The object is not strongly reachable.</li>
- * <li>There exists at least one path from the root set to the object that does traverse
- * a {@code java.lang.ref.SoftReference} instance, but no {@code java.lang.ref.WeakReference}
- * or {@code java.lang.ref.PhantomReference} instances.</li>
- * </ul> </td>
- * </tr>
- *
- * <tr>
- * <td>Weakly reachable</td>
- * <td> <ul>
- * <li>The object is neither strongly nor softly reachable.</li>
- * <li>There exists at least one path from the root set to the object that does traverse a
- * {@code java.lang.ref.WeakReference} instance, but no {@code java.lang.ref.PhantomReference}
- * instances.</li>
- * </ul> </td>
- * </tr>
- *
- * <tr>
- * <td>Phantom-reachable</td>
- * <td> <ul>
- * <li>The object is neither strongly, softly, nor weakly reachable.</li>
- * <li>The object is referenced by a {@code java.lang.ref.PhantomReference} instance.</li>
- * <li>The object has already been finalized.</li>
- * </ul> </td>
- * </tr>
- * </table>
- */
-public abstract class Reference<T> {
-
- /**
- * The object to which this reference refers.
- * VM requirement: this field <em>must</em> be called "referent"
- * and be an object.
- */
- volatile T referent;
-
- /**
- * If non-null, the queue on which this reference will be enqueued
- * when the referent is appropriately reachable.
- * VM requirement: this field <em>must</em> be called "queue"
- * and be a java.lang.ref.ReferenceQueue.
- */
- volatile ReferenceQueue<? super T> queue;
-
- /**
- * Used internally by java.lang.ref.ReferenceQueue.
- * VM requirement: this field <em>must</em> be called "queueNext"
- * and be a java.lang.ref.Reference.
- */
- @SuppressWarnings("unchecked")
- volatile Reference queueNext;
-
- /**
- * Used internally by the VM. This field forms a circular and
- * singly linked list of reference objects discovered by the
- * garbage collector and awaiting processing by the reference
- * queue thread.
- *
- * @hide
- */
- public volatile Reference<?> pendingNext;
-
- /**
- * Constructs a new instance of this class.
- */
- Reference() {
- }
-
- Reference(T r, ReferenceQueue<? super T> q) {
- referent = r;
- queue = q;
- }
-
- /**
- * Makes the referent {@code null}. This does not force the reference
- * object to be enqueued.
- */
- public void clear() {
- referent = null;
- }
-
- /**
- * Adds an object to its reference queue.
- *
- * @return {@code true} if this call has caused the {@code Reference} to
- * become enqueued, or {@code false} otherwise
- *
- * @hide
- */
- public final synchronized boolean enqueueInternal() {
- if (queue != null && queueNext == null) {
- queue.enqueue(this);
- queue = null;
- return true;
- }
- return false;
- }
-
- /**
- * Forces the reference object to be enqueued if it has been associated with
- * a queue.
- *
- * @return {@code true} if this call has caused the {@code Reference} to
- * become enqueued, or {@code false} otherwise
- */
- public boolean enqueue() {
- return enqueueInternal();
- }
-
- /**
- * Returns the referent of the reference object.
- *
- * @return the referent to which reference refers, or {@code null} if the
- * object has been cleared.
- */
- public T get() {
- return referent;
- }
-
- /**
- * Checks whether the reference object has been enqueued.
- *
- * @return {@code true} if the {@code Reference} has been enqueued, {@code
- * false} otherwise
- */
- public boolean isEnqueued() {
- return queueNext != null;
- }
-
-}
diff --git a/luni/src/main/java/java/lang/ref/ReferenceQueue.java b/luni/src/main/java/java/lang/ref/ReferenceQueue.java
index 2b8089c..4c78fbf 100644
--- a/luni/src/main/java/java/lang/ref/ReferenceQueue.java
+++ b/luni/src/main/java/java/lang/ref/ReferenceQueue.java
@@ -28,6 +28,7 @@ public class ReferenceQueue<T> {
private static final int NANOS_PER_MILLI = 1000000;
private Reference<? extends T> head;
+ private Reference<? extends T> tail;
/**
* Constructs a new instance of this class.
@@ -48,18 +49,16 @@ public class ReferenceQueue<T> {
return null;
}
- Reference<? extends T> ret;
+ Reference<? extends T> ret = head;
- ret = head;
-
- if (head == head.queueNext) {
+ if (head == tail) {
+ tail = null;
head = null;
} else {
head = head.queueNext;
}
ret.queueNext = null;
-
return ret;
}
@@ -133,12 +132,16 @@ public class ReferenceQueue<T> {
* reference object to be enqueued.
*/
synchronized void enqueue(Reference<? extends T> reference) {
- if (head == null) {
- reference.queueNext = reference;
+ if (tail == null) {
+ head = reference;
} else {
- reference.queueNext = head;
+ tail.queueNext = reference;
}
- head = reference;
+
+ // The newly enqueued reference becomes the new tail, and always
+ // points to itself.
+ tail = reference;
+ tail.queueNext = reference;
notify();
}
@@ -150,9 +153,18 @@ public class ReferenceQueue<T> {
if (unenqueued == null) {
unenqueued = list;
} else {
- Reference<?> next = unenqueued.pendingNext;
- unenqueued.pendingNext = list.pendingNext;
- list.pendingNext = next;
+ // Find the last element in unenqueued.
+ Reference<?> last = unenqueued;
+ while (last.pendingNext != unenqueued) {
+ last = last.pendingNext;
+ }
+ // Add our list to the end. Update the pendingNext to point back to enqueued.
+ last.pendingNext = list;
+ last = list;
+ while (last.pendingNext != list) {
+ last = last.pendingNext;
+ }
+ last.pendingNext = unenqueued;
}
ReferenceQueue.class.notifyAll();
}
diff --git a/luni/src/main/java/java/lang/reflect/Array.java b/luni/src/main/java/java/lang/reflect/Array.java
index 088a434..a7dacfe 100644
--- a/luni/src/main/java/java/lang/reflect/Array.java
+++ b/luni/src/main/java/java/lang/reflect/Array.java
@@ -352,16 +352,16 @@ public final class Array {
public static Object newInstance(Class<?> componentType, int size) throws NegativeArraySizeException {
if (!componentType.isPrimitive()) {
return createObjectArray(componentType, size);
- } else if (componentType == boolean.class) {
- return new boolean[size];
- } else if (componentType == byte.class) {
- return new byte[size];
} else if (componentType == char.class) {
return new char[size];
- } else if (componentType == short.class) {
- return new short[size];
} else if (componentType == int.class) {
return new int[size];
+ } else if (componentType == byte.class) {
+ return new byte[size];
+ } else if (componentType == boolean.class) {
+ return new boolean[size];
+ } else if (componentType == short.class) {
+ return new short[size];
} else if (componentType == long.class) {
return new long[size];
} else if (componentType == float.class) {
diff --git a/luni/src/main/java/java/lang/reflect/Modifier.java b/luni/src/main/java/java/lang/reflect/Modifier.java
index 5f973d5..257064e 100644
--- a/luni/src/main/java/java/lang/reflect/Modifier.java
+++ b/luni/src/main/java/java/lang/reflect/Modifier.java
@@ -106,7 +106,7 @@ public class Modifier {
* require they implement.
* @hide
*/
- public static final int MIRANDA = 0x8000;
+ public static final int MIRANDA = 0x200000;
/**
* Dex addition to mark instance constructors and static class
diff --git a/luni/src/main/java/java/math/BigDecimal.java b/luni/src/main/java/java/math/BigDecimal.java
index 03ce8dd..f735607 100644
--- a/luni/src/main/java/java/math/BigDecimal.java
+++ b/luni/src/main/java/java/math/BigDecimal.java
@@ -1025,6 +1025,14 @@ public class BigDecimal extends Number implements Comparable<BigDecimal>, Serial
}
long diffScale = ((long)this.scale - divisor.scale) - scale;
+
+ // Check whether the diffScale will fit into an int. See http://b/17393664.
+ if (bitLength(diffScale) > 32) {
+ throw new ArithmeticException(
+ "Unable to perform divisor / dividend scaling: the difference in scale is too" +
+ " big (" + diffScale + ")");
+ }
+
if(this.bitLength < 64 && divisor.bitLength < 64 ) {
if(diffScale == 0) {
return dividePrimitiveLongs(this.smallValue,
diff --git a/luni/src/main/java/java/math/Multiplication.java b/luni/src/main/java/java/math/Multiplication.java
index 98cabee..093b1b7 100644
--- a/luni/src/main/java/java/math/Multiplication.java
+++ b/luni/src/main/java/java/math/Multiplication.java
@@ -125,49 +125,45 @@ class Multiplication {
} else if (exp <= 50) {
// To calculate: 10^exp
return BigInteger.TEN.pow(intExp);
- } else if (exp <= 1000) {
- // To calculate: 5^exp * 2^exp
- return bigFivePows[1].pow(intExp).shiftLeft(intExp);
}
- // "LARGE POWERS"
- /*
- * To check if there is free memory to allocate a BigInteger of the
- * estimated size, measured in bytes: 1 + [exp / log10(2)]
- */
- long byteArraySize = 1 + (long)(exp / 2.4082399653118496);
-
- if (byteArraySize > Runtime.getRuntime().freeMemory()) {
- throw new ArithmeticException();
- }
- if (exp <= Integer.MAX_VALUE) {
- // To calculate: 5^exp * 2^exp
- return bigFivePows[1].pow(intExp).shiftLeft(intExp);
- }
- /*
- * "HUGE POWERS"
- *
- * This branch probably won't be executed since the power of ten is too
- * big.
- */
- // To calculate: 5^exp
- BigInteger powerOfFive = bigFivePows[1].pow(Integer.MAX_VALUE);
- BigInteger res = powerOfFive;
- long longExp = exp - Integer.MAX_VALUE;
-
- intExp = (int)(exp % Integer.MAX_VALUE);
- while (longExp > Integer.MAX_VALUE) {
- res = res.multiply(powerOfFive);
- longExp -= Integer.MAX_VALUE;
- }
- res = res.multiply(bigFivePows[1].pow(intExp));
- // To calculate: 5^exp << exp
- res = res.shiftLeft(Integer.MAX_VALUE);
- longExp = exp - Integer.MAX_VALUE;
- while (longExp > Integer.MAX_VALUE) {
- res = res.shiftLeft(Integer.MAX_VALUE);
- longExp -= Integer.MAX_VALUE;
+
+ BigInteger res = null;
+ try {
+ // "LARGE POWERS"
+ if (exp <= Integer.MAX_VALUE) {
+ // To calculate: 5^exp * 2^exp
+ res = bigFivePows[1].pow(intExp).shiftLeft(intExp);
+ } else {
+ /*
+ * "HUGE POWERS"
+ *
+ * This branch probably won't be executed since the power of ten is too
+ * big.
+ */
+ // To calculate: 5^exp
+ BigInteger powerOfFive = bigFivePows[1].pow(Integer.MAX_VALUE);
+ res = powerOfFive;
+ long longExp = exp - Integer.MAX_VALUE;
+
+ intExp = (int) (exp % Integer.MAX_VALUE);
+ while (longExp > Integer.MAX_VALUE) {
+ res = res.multiply(powerOfFive);
+ longExp -= Integer.MAX_VALUE;
+ }
+ res = res.multiply(bigFivePows[1].pow(intExp));
+ // To calculate: 5^exp << exp
+ res = res.shiftLeft(Integer.MAX_VALUE);
+ longExp = exp - Integer.MAX_VALUE;
+ while (longExp > Integer.MAX_VALUE) {
+ res = res.shiftLeft(Integer.MAX_VALUE);
+ longExp -= Integer.MAX_VALUE;
+ }
+ res = res.shiftLeft(intExp);
+ }
+ } catch (OutOfMemoryError error) {
+ throw new ArithmeticException(error.getMessage());
}
- res = res.shiftLeft(intExp);
+
return res;
}
diff --git a/luni/src/main/java/java/net/AddressCache.java b/luni/src/main/java/java/net/AddressCache.java
index 194761a..2aba78b 100644
--- a/luni/src/main/java/java/net/AddressCache.java
+++ b/luni/src/main/java/java/net/AddressCache.java
@@ -37,8 +37,36 @@ class AddressCache {
private static final long TTL_NANOS = 2 * 1000000000L;
// The actual cache.
- private final BasicLruCache<String, AddressCacheEntry> cache
- = new BasicLruCache<String, AddressCacheEntry>(MAX_ENTRIES);
+ private final BasicLruCache<AddressCacheKey, AddressCacheEntry> cache
+ = new BasicLruCache<AddressCacheKey, AddressCacheEntry>(MAX_ENTRIES);
+
+ static class AddressCacheKey {
+ private final String mHostname;
+ private final int mNetId;
+
+ AddressCacheKey(String hostname, int netId) {
+ mHostname = hostname;
+ mNetId = netId;
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof AddressCacheKey)) {
+ return false;
+ }
+ AddressCacheKey lhs = (AddressCacheKey) o;
+ return mHostname.equals(lhs.mHostname) && mNetId == lhs.mNetId;
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + mNetId;
+ result = 31 * result + mHostname.hashCode();
+ return result;
+ }
+ }
static class AddressCacheEntry {
// Either an InetAddress[] for a positive entry,
@@ -67,12 +95,12 @@ class AddressCache {
}
/**
- * Returns the cached InetAddress[] associated with 'hostname'. Returns null if nothing is known
- * about 'hostname'. Returns a String suitable for use as an UnknownHostException detail
- * message if 'hostname' is known not to exist.
+ * Returns the cached InetAddress[] for 'hostname' on network 'netId'. Returns null
+ * if nothing is known about 'hostname'. Returns a String suitable for use as an
+ * UnknownHostException detail message if 'hostname' is known not to exist.
*/
- public Object get(String hostname) {
- AddressCacheEntry entry = cache.get(hostname);
+ public Object get(String hostname, int netId) {
+ AddressCacheEntry entry = cache.get(new AddressCacheKey(hostname, netId));
// Do we have a valid cache entry?
if (entry != null && entry.expiryNanos >= System.nanoTime()) {
return entry.value;
@@ -86,15 +114,15 @@ class AddressCache {
* Associates the given 'addresses' with 'hostname'. The association will expire after a
* certain length of time.
*/
- public void put(String hostname, InetAddress[] addresses) {
- cache.put(hostname, new AddressCacheEntry(addresses));
+ public void put(String hostname, int netId, InetAddress[] addresses) {
+ cache.put(new AddressCacheKey(hostname, netId), new AddressCacheEntry(addresses));
}
/**
* Records that 'hostname' is known not to have any associated addresses. (I.e. insert a
* negative cache entry.)
*/
- public void putUnknownHost(String hostname, String detailMessage) {
- cache.put(hostname, new AddressCacheEntry(detailMessage));
+ public void putUnknownHost(String hostname, int netId, String detailMessage) {
+ cache.put(new AddressCacheKey(hostname, netId), new AddressCacheEntry(detailMessage));
}
}
diff --git a/luni/src/main/java/java/net/DatagramSocket.java b/luni/src/main/java/java/net/DatagramSocket.java
index 49f141a..f9b72d8 100644
--- a/luni/src/main/java/java/net/DatagramSocket.java
+++ b/luni/src/main/java/java/net/DatagramSocket.java
@@ -17,14 +17,14 @@
package java.net;
+import android.system.ErrnoException;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.channels.DatagramChannel;
-import libcore.io.ErrnoException;
import libcore.io.IoBridge;
import libcore.io.Libcore;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* This class implements a UDP socket for sending and receiving {@code
@@ -114,6 +114,17 @@ public class DatagramSocket implements Closeable {
}
/**
+ * Sets the DatagramSocket and its related DatagramSocketImpl state as if a successful close()
+ * took place, without actually performing an OS close().
+ *
+ * @hide used in java.nio
+ */
+ public void onClose() {
+ isClosed = true;
+ impl.onClose();
+ }
+
+ /**
* Disconnects this UDP datagram socket from the remote host. This method
* called on an unconnected socket does nothing.
*/
@@ -127,6 +138,19 @@ public class DatagramSocket implements Closeable {
isConnected = false;
}
+ /**
+ * Sets the DatagramSocket and its related DatagramSocketImpl state as if a successful
+ * disconnect() took place, without actually performing a disconnect().
+ *
+ * @hide used in java.nio
+ */
+ public void onDisconnect() {
+ address = null;
+ port = -1;
+ isConnected = false;
+ impl.onDisconnect();
+ }
+
synchronized void createSocket(int aPort, InetAddress addr) throws SocketException {
impl = factory != null ? factory.createDatagramSocketImpl()
: new PlainDatagramSocketImpl();
@@ -152,8 +176,8 @@ public class DatagramSocket implements Closeable {
}
/**
- * Returns the local address to which this socket is bound,
- * or {@code null} if this socket is closed.
+ * Returns the local address to which this socket is bound, a wildcard address if this
+ * socket is not yet bound, or {@code null} if this socket is closed.
*/
public InetAddress getLocalAddress() {
try {
@@ -439,9 +463,12 @@ public class DatagramSocket implements Closeable {
*/
public void bind(SocketAddress localAddr) throws SocketException {
checkOpen();
- int localPort = 0;
- InetAddress addr = Inet4Address.ANY;
- if (localAddr != null) {
+ int localPort;
+ InetAddress addr;
+ if (localAddr == null) {
+ localPort = 0;
+ addr = Inet4Address.ANY;
+ } else {
if (!(localAddr instanceof InetSocketAddress)) {
throw new IllegalArgumentException("Local address not an InetSocketAddress: " +
localAddr.getClass());
@@ -459,6 +486,17 @@ public class DatagramSocket implements Closeable {
}
/**
+ * Sets the DatagramSocket and its related DatagramSocketImpl state as if a successful bind()
+ * took place, without actually performing an OS bind().
+ *
+ * @hide used in java.nio
+ */
+ public void onBind(InetAddress localAddress, int localPort) {
+ isBound = true;
+ impl.onBind(localAddress, localPort);
+ }
+
+ /**
* Connects this datagram socket to the address and port specified by {@code peer}.
* Future calls to {@link #send} will use this as the default target, and {@link #receive}
* will only accept packets from this source.
@@ -492,6 +530,19 @@ public class DatagramSocket implements Closeable {
}
/**
+ * Sets the DatagramSocket and its related DatagramSocketImpl state as if a successful connect()
+ * took place, without actually performing an OS connect().
+ *
+ * @hide used in java.nio
+ */
+ public void onConnect(InetAddress remoteAddress, int remotePort) {
+ isConnected = true;
+ this.address = remoteAddress;
+ this.port = remotePort;
+ impl.onConnect(remoteAddress, remotePort);
+ }
+
+ /**
* Connects this datagram socket to the specific {@code address} and {@code port}.
* Future calls to {@link #send} will use this as the default target, and {@link #receive}
* will only accept packets from this source.
@@ -537,10 +588,11 @@ public class DatagramSocket implements Closeable {
}
/**
- * Returns the {@code SocketAddress} this socket is bound to, or null for an unbound socket.
+ * Returns the {@code SocketAddress} this socket is bound to, or {@code null} for an unbound or
+ * closed socket.
*/
public SocketAddress getLocalSocketAddress() {
- if (!isBound()) {
+ if (isClosed() || !isBound()) {
return null;
}
return new InetSocketAddress(getLocalAddress(), getLocalPort());
diff --git a/luni/src/main/java/java/net/DatagramSocketImpl.java b/luni/src/main/java/java/net/DatagramSocketImpl.java
index 097eb17..1a39987 100644
--- a/luni/src/main/java/java/net/DatagramSocketImpl.java
+++ b/luni/src/main/java/java/net/DatagramSocketImpl.java
@@ -268,4 +268,40 @@ public abstract class DatagramSocketImpl implements SocketOptions {
* if an error occurs while peeking at the data.
*/
protected abstract int peekData(DatagramPacket pack) throws IOException;
+
+ /**
+ * Initialize the bind() state.
+ * @hide used in java.nio.
+ */
+ protected void onBind(InetAddress localAddress, int localPort) {
+ // Do not add any code to these methods. They are concrete only to preserve API
+ // compatibility.
+ }
+
+ /**
+ * Initialize the connect() state.
+ * @hide used in java.nio.
+ */
+ protected void onConnect(InetAddress remoteAddress, int remotePort) {
+ // Do not add any code to these methods. They are concrete only to preserve API
+ // compatibility.
+ }
+
+ /**
+ * Initialize the disconnected state.
+ * @hide used in java.nio.
+ */
+ protected void onDisconnect() {
+ // Do not add any code to these methods. They are concrete only to preserve API
+ // compatibility.
+ }
+
+ /**
+ * Initialize the closed state.
+ * @hide used in java.nio.
+ */
+ protected void onClose() {
+ // Do not add any code to these methods. They are concrete only to preserve API
+ // compatibility.
+ }
}
diff --git a/luni/src/main/java/java/net/HttpCookie.java b/luni/src/main/java/java/net/HttpCookie.java
index ce1a8d2..dd81fd6 100644
--- a/luni/src/main/java/java/net/HttpCookie.java
+++ b/luni/src/main/java/java/net/HttpCookie.java
@@ -53,10 +53,12 @@ import libcore.util.Objects;
* in this format is {@code 1}.
* </ul>
*
- * <p>This implementation silently discards unrecognized attributes. In
- * particular, the {@code HttpOnly} attribute is widely served but isn't in any
- * of the above specs. It was introduced by Internet Explorer to prevent server
- * cookies from being exposed in the DOM to JavaScript, etc.
+ * <p>Support for the "HttpOnly" attribute specified in
+ * <a href="http://tools.ietf.org/html/rfc6265">RFC 6265</a> is also included. RFC 6265 is intended
+ * to obsolete RFC 2965. Support for features from RFC 2965 that have been deprecated by RFC 6265
+ * such as Cookie2, Set-Cookie2 headers and version information remain supported by this class.
+ *
+ * <p>This implementation silently discards unrecognized attributes.
*
* @since 1.6
*/
@@ -65,16 +67,17 @@ public final class HttpCookie implements Cloneable {
private static final Set<String> RESERVED_NAMES = new HashSet<String>();
static {
- RESERVED_NAMES.add("comment"); // RFC 2109 RFC 2965
- RESERVED_NAMES.add("commenturl"); // RFC 2965
- RESERVED_NAMES.add("discard"); // RFC 2965
- RESERVED_NAMES.add("domain"); // Netscape RFC 2109 RFC 2965
+ RESERVED_NAMES.add("comment"); // RFC 2109 RFC 2965 RFC 6265
+ RESERVED_NAMES.add("commenturl"); // RFC 2965 RFC 6265
+ RESERVED_NAMES.add("discard"); // RFC 2965 RFC 6265
+ RESERVED_NAMES.add("domain"); // Netscape RFC 2109 RFC 2965 RFC 6265
RESERVED_NAMES.add("expires"); // Netscape
- RESERVED_NAMES.add("max-age"); // RFC 2109 RFC 2965
- RESERVED_NAMES.add("path"); // Netscape RFC 2109 RFC 2965
- RESERVED_NAMES.add("port"); // RFC 2965
- RESERVED_NAMES.add("secure"); // Netscape RFC 2109 RFC 2965
- RESERVED_NAMES.add("version"); // RFC 2109 RFC 2965
+ RESERVED_NAMES.add("httponly"); // RFC 6265
+ RESERVED_NAMES.add("max-age"); // RFC 2109 RFC 2965 RFC 6265
+ RESERVED_NAMES.add("path"); // Netscape RFC 2109 RFC 2965 RFC 6265
+ RESERVED_NAMES.add("port"); // RFC 2965 RFC 6265
+ RESERVED_NAMES.add("secure"); // Netscape RFC 2109 RFC 2965 RFC 6265
+ RESERVED_NAMES.add("version"); // RFC 2109 RFC 2965 RFC 6265
}
/**
@@ -332,14 +335,26 @@ public final class HttpCookie implements Cloneable {
}
}
} else if (name.equals("max-age") && cookie.maxAge == -1L) {
- hasMaxAge = true;
- cookie.maxAge = Long.parseLong(value);
+ // RFCs 2109 and 2965 suggests a zero max-age as a way of deleting a cookie.
+ // RFC 6265 specifies the value must be > 0 but also describes what to do if the
+ // value is negative, zero or non-numeric in section 5.2.2. The RI does none of this
+ // and accepts negative, positive values and throws an IllegalArgumentException
+ // if the value is non-numeric.
+ try {
+ long maxAge = Long.parseLong(value);
+ hasMaxAge = true;
+ cookie.maxAge = maxAge;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid max-age: " + value);
+ }
} else if (name.equals("path") && cookie.path == null) {
cookie.path = value;
} else if (name.equals("port") && cookie.portList == null) {
cookie.portList = value != null ? value : "";
} else if (name.equals("secure")) {
cookie.secure = true;
+ } else if (name.equals("httponly")) {
+ cookie.httpOnly = true;
} else if (name.equals("version") && !hasVersion) {
cookie.version = Integer.parseInt(value);
}
@@ -430,6 +445,7 @@ public final class HttpCookie implements Cloneable {
private String path;
private String portList;
private boolean secure;
+ private boolean httpOnly;
private String value;
private int version = 1;
@@ -698,7 +714,21 @@ public final class HttpCookie implements Cloneable {
/**
* Returns a string representing this cookie in the format used by the
- * {@code Cookie} header line in an HTTP request.
+ * {@code Cookie} header line in an HTTP request as specified by RFC 2965 section 3.3.4.
+ *
+ * <p>The resulting string does not include a "Cookie:" prefix or any version information.
+ * The returned {@code String} is not suitable for passing to {@link #parse(String)}: Several of
+ * the attributes that would be needed to preserve all of the cookie's information are omitted.
+ * The String is formatted for an HTTP request not an HTTP response.
+ *
+ * <p>The attributes included and the format depends on the cookie's {@code version}:
+ * <ul>
+ * <li>Version 0: Includes only the name and value. Conforms to RFC 2965 (for
+ * version 0 cookies). This should also be used to conform with RFC 6265.
+ * </li>
+ * <li>Version 1: Includes the name and value, and Path, Domain and Port attributes.
+ * Conforms to RFC 2965 (for version 1 cookies).</li>
+ * </ul>
*/
@Override public String toString() {
if (version == 0) {
diff --git a/luni/src/main/java/java/net/HttpURLConnection.java b/luni/src/main/java/java/net/HttpURLConnection.java
index 89a4bc4..4e5b4ee 100644
--- a/luni/src/main/java/java/net/HttpURLConnection.java
+++ b/luni/src/main/java/java/net/HttpURLConnection.java
@@ -784,11 +784,15 @@ public abstract class HttpURLConnection extends URLConnection {
* only servers may not support this mode.
*
* <p>When HTTP chunked encoding is used, the stream is divided into
- * chunks, each prefixed with a header containing the chunk's size. Setting
- * a large chunk length requires a large internal buffer, potentially
- * wasting memory. Setting a small chunk length increases the number of
+ * chunks, each prefixed with a header containing the chunk's size.
+ * A large chunk length requires a large internal buffer, potentially
+ * wasting memory. A small chunk length increases the number of
* bytes that must be transmitted because of the header on every chunk.
- * Most caller should use {@code 0} to get the system default.
+ *
+ * <p>Implementation details: In some releases the {@code chunkLength} is
+ * treated as a hint: chunks sent to the server may actually be larger or
+ * smaller. To force a chunk to be sent to the server call
+ * {@link java.io.OutputStream#flush()}.
*
* @see #setFixedLengthStreamingMode
* @param chunkLength the length to use, or {@code 0} for the default chunk
diff --git a/luni/src/main/java/java/net/Inet4Address.java b/luni/src/main/java/java/net/Inet4Address.java
index 7c26639..f0b1b5b 100644
--- a/luni/src/main/java/java/net/Inet4Address.java
+++ b/luni/src/main/java/java/net/Inet4Address.java
@@ -20,7 +20,7 @@ package java.net;
import java.io.ObjectStreamException;
import java.nio.ByteOrder;
import libcore.io.Memory;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* An IPv4 address. See {@link InetAddress}.
diff --git a/luni/src/main/java/java/net/Inet6Address.java b/luni/src/main/java/java/net/Inet6Address.java
index 37e9c18..8ab0f8d 100644
--- a/luni/src/main/java/java/net/Inet6Address.java
+++ b/luni/src/main/java/java/net/Inet6Address.java
@@ -23,7 +23,7 @@ import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.util.Arrays;
import java.util.Enumeration;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* An IPv6 address. See {@link InetAddress}.
diff --git a/luni/src/main/java/java/net/InetAddress.java b/luni/src/main/java/java/net/InetAddress.java
index 98ad098..5cfa15a 100644
--- a/luni/src/main/java/java/net/InetAddress.java
+++ b/luni/src/main/java/java/net/InetAddress.java
@@ -17,6 +17,9 @@
package java.net;
+import android.system.ErrnoException;
+import android.system.GaiException;
+import android.system.StructAddrinfo;
import dalvik.system.BlockGuard;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -28,18 +31,13 @@ import java.io.Serializable;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Comparator;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
-import libcore.io.ErrnoException;
-import libcore.io.GaiException;
+import java.util.concurrent.CountDownLatch;
+import java.util.List;
import libcore.io.IoBridge;
import libcore.io.Libcore;
import libcore.io.Memory;
-import libcore.io.StructAddrinfo;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* An Internet Protocol (IP) address. This can be either an IPv4 address or an IPv6 address, and
@@ -129,6 +127,9 @@ public class InetAddress implements Serializable {
private static final long serialVersionUID = 3286316764910316507L;
+ /** Using NetID of NETID_UNSET indicates resolution should be done on default network. */
+ private static final int NETID_UNSET = 0;
+
private int family;
byte[] ipaddress;
@@ -211,14 +212,29 @@ public class InetAddress implements Serializable {
* @throws UnknownHostException if the address lookup fails.
*/
public static InetAddress[] getAllByName(String host) throws UnknownHostException {
- return getAllByNameImpl(host).clone();
+ return getAllByNameImpl(host, NETID_UNSET).clone();
}
/**
- * Returns the InetAddresses for {@code host}. The returned array is shared
- * and must be cloned before it is returned to application code.
+ * Operates identically to {@code getAllByName} except host resolution is
+ * performed on the network designated by {@code netId}.
+ *
+ * @param host the hostname or literal IP string to be resolved.
+ * @param netId the network to use for host resolution.
+ * @return the array of addresses associated with the specified host.
+ * @throws UnknownHostException if the address lookup fails.
+ * @hide internal use only
*/
- private static InetAddress[] getAllByNameImpl(String host) throws UnknownHostException {
+ public static InetAddress[] getAllByNameOnNet(String host, int netId) throws UnknownHostException {
+ return getAllByNameImpl(host, netId).clone();
+ }
+
+ /**
+ * Returns the InetAddresses for {@code host} on network {@code netId}. The
+ * returned array is shared and must be cloned before it is returned to
+ * application code.
+ */
+ private static InetAddress[] getAllByNameImpl(String host, int netId) throws UnknownHostException {
if (host == null || host.isEmpty()) {
return loopbackAddresses();
}
@@ -233,7 +249,7 @@ public class InetAddress implements Serializable {
return new InetAddress[] { result };
}
- return lookupHostByName(host).clone();
+ return lookupHostByName(host, netId).clone();
}
private static InetAddress makeInetAddress(byte[] bytes, String hostName) throws UnknownHostException {
@@ -266,7 +282,7 @@ public class InetAddress implements Serializable {
hints.ai_flags = AI_NUMERICHOST;
InetAddress[] addresses = null;
try {
- addresses = Libcore.os.getaddrinfo(address, hints);
+ addresses = Libcore.os.android_getaddrinfo(address, hints, NETID_UNSET);
} catch (GaiException ignored) {
}
return (addresses != null) ? addresses[0] : null;
@@ -286,7 +302,22 @@ public class InetAddress implements Serializable {
* if the address lookup fails.
*/
public static InetAddress getByName(String host) throws UnknownHostException {
- return getAllByNameImpl(host)[0];
+ return getAllByNameImpl(host, NETID_UNSET)[0];
+ }
+
+ /**
+ * Operates identically to {@code getByName} except host resolution is
+ * performed on the network designated by {@code netId}.
+ *
+ * @param host
+ * the hostName to be resolved to an address or {@code null}.
+ * @param netId the network to use for host resolution.
+ * @return the {@code InetAddress} instance representing the host.
+ * @throws UnknownHostException if the address lookup fails.
+ * @hide internal use only
+ */
+ public static InetAddress getByNameOnNet(String host, int netId) throws UnknownHostException {
+ return getAllByNameImpl(host, netId)[0];
}
/**
@@ -362,7 +393,7 @@ public class InetAddress implements Serializable {
*/
public static InetAddress getLocalHost() throws UnknownHostException {
String host = Libcore.os.uname().nodename;
- return lookupHostByName(host)[0];
+ return lookupHostByName(host, NETID_UNSET)[0];
}
/**
@@ -379,12 +410,14 @@ public class InetAddress implements Serializable {
* Resolves a hostname to its IP addresses using a cache.
*
* @param host the hostname to resolve.
+ * @param netId the network to perform resolution upon.
* @return the IP addresses of the host.
*/
- private static InetAddress[] lookupHostByName(String host) throws UnknownHostException {
+ private static InetAddress[] lookupHostByName(String host, int netId)
+ throws UnknownHostException {
BlockGuard.getThreadPolicy().onNetwork();
// Do we have a result cached?
- Object cachedResult = addressCache.get(host);
+ Object cachedResult = addressCache.get(host, netId);
if (cachedResult != null) {
if (cachedResult instanceof InetAddress[]) {
// A cached positive result.
@@ -402,12 +435,12 @@ public class InetAddress implements Serializable {
// for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
// anyway, just pick one.
hints.ai_socktype = SOCK_STREAM;
- InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints);
+ InetAddress[] addresses = Libcore.os.android_getaddrinfo(host, hints, netId);
// TODO: should getaddrinfo set the hostname of the InetAddresses it returns?
for (InetAddress address : addresses) {
address.hostName = host;
}
- addressCache.put(host, addresses);
+ addressCache.put(host, netId, addresses);
return addresses;
} catch (GaiException gaiException) {
// If the failure appears to have been a lack of INTERNET permission, throw a clear
@@ -420,7 +453,7 @@ public class InetAddress implements Serializable {
}
// Otherwise, throw an UnknownHostException.
String detailMessage = "Unable to resolve host \"" + host + "\": " + Libcore.os.gai_strerror(gaiException.error);
- addressCache.putUnknownHost(host, detailMessage);
+ addressCache.putUnknownHost(host, netId, detailMessage);
throw gaiException.rethrowAsUnknownHostException(detailMessage);
}
}
@@ -734,7 +767,7 @@ public class InetAddress implements Serializable {
}
}
- IoBridge.closeSocket(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
return reached;
}
diff --git a/luni/src/main/java/java/net/InetUnixAddress.java b/luni/src/main/java/java/net/InetUnixAddress.java
index 44b9cba..51236e2 100644
--- a/luni/src/main/java/java/net/InetUnixAddress.java
+++ b/luni/src/main/java/java/net/InetUnixAddress.java
@@ -18,7 +18,7 @@ package java.net;
import java.nio.charset.StandardCharsets;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* An AF_UNIX address. See {@link InetAddress}.
diff --git a/luni/src/main/java/java/net/JarURLConnection.java b/luni/src/main/java/java/net/JarURLConnection.java
index 4b84893..e5c8fac 100644
--- a/luni/src/main/java/java/net/JarURLConnection.java
+++ b/luni/src/main/java/java/net/JarURLConnection.java
@@ -18,11 +18,13 @@
package java.net;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
+import libcore.net.UriCodec;
/**
* This class establishes a connection to a {@code jar:} URL using the {@code
@@ -64,12 +66,13 @@ public abstract class JarURLConnection extends URLConnection {
*/
protected JarURLConnection(URL url) throws MalformedURLException {
super(url);
- file = url.getFile();
+ file = decode(url.getFile());
+
int sepIdx;
if ((sepIdx = file.indexOf("!/")) < 0) {
throw new MalformedURLException();
}
- fileURL = new URL(url.getFile().substring(0,sepIdx));
+ fileURL = new URL(file.substring(0, sepIdx));
sepIdx += 2;
if (file.length() == sepIdx) {
return;
@@ -189,4 +192,17 @@ public abstract class JarURLConnection extends URLConnection {
Manifest m = getJarFile().getManifest();
return (m == null) ? null : m.getMainAttributes();
}
+
+ private static String decode(String encoded) throws MalformedURLException {
+ try {
+ // "+" means "+" in URLs. i.e. like RFC 3986, not like
+ // MIME application/x-www-form-urlencoded
+ final boolean convertPlus = false;
+ return UriCodec.decode(
+ encoded, convertPlus, StandardCharsets.UTF_8, true /* throwOnFailure */);
+ } catch (IllegalArgumentException e) {
+ throw new MalformedURLException("Unable to decode URL", e);
+ }
+ }
+
}
diff --git a/luni/src/main/java/java/net/MulticastSocket.java b/luni/src/main/java/java/net/MulticastSocket.java
index 6f4a582..24e66c5 100644
--- a/luni/src/main/java/java/net/MulticastSocket.java
+++ b/luni/src/main/java/java/net/MulticastSocket.java
@@ -229,6 +229,9 @@ public class MulticastSocket extends DatagramSocket {
private void checkJoinOrLeave(InetAddress groupAddr) throws IOException {
checkOpen();
+ if (groupAddr == null) {
+ throw new IllegalArgumentException("groupAddress == null");
+ }
if (!groupAddr.isMulticastAddress()) {
throw new IOException("Not a multicast group: " + groupAddr);
}
@@ -351,7 +354,8 @@ public class MulticastSocket extends DatagramSocket {
/**
* Disables multicast loopback if {@code disable == true}.
* See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the
- * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}.
+ * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}: true means disabled, false
+ * means enabled.
*
* @throws SocketException if an error occurs.
*/
diff --git a/luni/src/main/java/java/net/NetworkInterface.java b/luni/src/main/java/java/net/NetworkInterface.java
index 3128b98..852c09b 100644
--- a/luni/src/main/java/java/net/NetworkInterface.java
+++ b/luni/src/main/java/java/net/NetworkInterface.java
@@ -17,6 +17,7 @@
package java.net;
+import android.system.ErrnoException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -26,10 +27,9 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
import libcore.io.Libcore;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* This class is used to represent a network interface of the local device. An
diff --git a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
index 3226527..eb0c99d 100644
--- a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
@@ -17,23 +17,15 @@
package java.net;
+import android.system.ErrnoException;
+import android.system.StructGroupReq;
import dalvik.system.CloseGuard;
import java.io.FileDescriptor;
import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.DatagramSocketImpl;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.NetworkInterface;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import libcore.io.ErrnoException;
import libcore.io.IoBridge;
import libcore.io.Libcore;
-import libcore.io.StructGroupReq;
import libcore.util.EmptyArray;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* @hide used in java.nio.
@@ -78,15 +70,25 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl {
}
@Override
+ protected void onBind(InetAddress localAddress, int localPort) {
+ this.localPort = localPort;
+ }
+
+ @Override
public synchronized void close() {
guard.close();
try {
- IoBridge.closeSocket(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
} catch (IOException ignored) {
}
}
@Override
+ protected void onClose() {
+ guard.close();
+ }
+
+ @Override
public void create() throws SocketException {
this.fd = IoBridge.socket(false);
}
@@ -179,7 +181,8 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl {
public void send(DatagramPacket packet) throws IOException {
int port = isNativeConnected ? 0 : packet.getPort();
InetAddress address = isNativeConnected ? null : packet.getAddress();
- IoBridge.sendto(fd, packet.getData(), packet.getOffset(), packet.getLength(), 0, address, port);
+ IoBridge.sendto(fd, packet.getData(), packet.getOffset(), packet.getLength(), 0, address,
+ port);
}
public void setOption(int option, Object value) throws SocketException {
@@ -211,6 +214,13 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl {
}
@Override
+ protected void onConnect(InetAddress remoteAddress, int remotePort) {
+ isNativeConnected = true;
+ connectedAddress = remoteAddress;
+ connectedPort = remotePort;
+ }
+
+ @Override
public void disconnect() {
try {
Libcore.os.connect(fd, InetAddress.UNSPECIFIED, 0);
@@ -224,6 +234,13 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl {
isNativeConnected = false;
}
+ @Override
+ protected void onDisconnect() {
+ connectedPort = -1;
+ connectedAddress = null;
+ isNativeConnected = false;
+ }
+
/**
* Set the received address and port in the packet. We do this when the
* Datagram socket is connected at the native level and the
diff --git a/luni/src/main/java/java/net/PlainSocketImpl.java b/luni/src/main/java/java/net/PlainSocketImpl.java
index 18942d6..4e5ba44 100644
--- a/luni/src/main/java/java/net/PlainSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainSocketImpl.java
@@ -17,28 +17,19 @@
package java.net;
+import android.system.ErrnoException;
import dalvik.system.CloseGuard;
import java.io.FileDescriptor;
-import java.io.IOException;
import java.io.InputStream;
+import java.io.IOException;
import java.io.OutputStream;
-import java.net.ConnectException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.net.SocketImpl;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
import java.nio.ByteOrder;
import java.util.Arrays;
-import libcore.io.ErrnoException;
import libcore.io.IoBridge;
import libcore.io.Libcore;
import libcore.io.Memory;
import libcore.io.Streams;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* @hide used in java.nio.
@@ -120,15 +111,6 @@ public class PlainSocketImpl extends SocketImpl {
return proxy != null && proxy.type() == Proxy.Type.SOCKS;
}
- public void initLocalPort(int localPort) {
- this.localport = localPort;
- }
-
- public void initRemoteAddressAndPort(InetAddress remoteAddress, int remotePort) {
- this.address = remoteAddress;
- this.port = remotePort;
- }
-
private void checkNotClosed() throws IOException {
if (!fd.valid()) {
throw new SocketException("Socket is closed");
@@ -148,7 +130,6 @@ public class PlainSocketImpl extends SocketImpl {
@Override protected void bind(InetAddress address, int port) throws IOException {
IoBridge.bind(fd, address, port);
- this.address = address;
if (port != 0) {
this.localport = port;
} else {
@@ -157,9 +138,19 @@ public class PlainSocketImpl extends SocketImpl {
}
@Override
+ public void onBind(InetAddress localAddress, int localPort) {
+ localport = localPort;
+ }
+
+ @Override
protected synchronized void close() throws IOException {
guard.close();
- IoBridge.closeSocket(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
+ }
+
+ @Override
+ public void onClose() {
+ guard.close();
}
@Override
@@ -191,8 +182,14 @@ public class PlainSocketImpl extends SocketImpl {
} else {
IoBridge.connect(fd, normalAddr, aPort, timeout);
}
- super.address = normalAddr;
- super.port = aPort;
+ address = normalAddr;
+ port = aPort;
+ }
+
+ @Override
+ public void onConnect(InetAddress remoteAddress, int remotePort) {
+ address = remoteAddress;
+ port = remotePort;
}
@Override
diff --git a/luni/src/main/java/java/net/ServerSocket.java b/luni/src/main/java/java/net/ServerSocket.java
index 399511f..72b197f 100644
--- a/luni/src/main/java/java/net/ServerSocket.java
+++ b/luni/src/main/java/java/net/ServerSocket.java
@@ -21,6 +21,8 @@ import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.ServerSocketChannel;
+import libcore.io.IoBridge;
+
/**
* This class represents a server-side socket that waits for incoming client
* connections. A {@code ServerSocket} handles the requests and sends back an
@@ -49,6 +51,8 @@ public class ServerSocket implements Closeable {
private boolean isClosed;
+ private InetAddress localAddress;
+
/**
* Constructs a new unbound {@code ServerSocket}.
*
@@ -99,7 +103,7 @@ public class ServerSocket implements Closeable {
impl.create(true);
try {
impl.bind(addr, port);
- isBound = true;
+ readBackBindState();
impl.listen(backlog > 0 ? backlog : DEFAULT_BACKLOG);
} catch (IOException e) {
close();
@@ -109,6 +113,14 @@ public class ServerSocket implements Closeable {
}
/**
+ * Read the cached isBound and localAddress state from the underlying OS socket.
+ */
+ private void readBackBindState() throws SocketException {
+ localAddress = IoBridge.getSocketLocalAddress(impl.fd);
+ isBound = true;
+ }
+
+ /**
* Waits for an incoming request and blocks until the connection is opened.
* This method returns a socket object representing the just opened
* connection.
@@ -152,8 +164,8 @@ public class ServerSocket implements Closeable {
}
/**
- * Gets the local IP address of this server socket or {@code null} if the
- * socket is unbound. This is useful for multihomed hosts.
+ * Gets the local IP address of this server socket if this socket has ever been bound,
+ * {@code null} otherwise. This is useful for multihomed hosts.
*
* @return the local address of this server socket.
*/
@@ -161,12 +173,13 @@ public class ServerSocket implements Closeable {
if (!isBound()) {
return null;
}
- return impl.getInetAddress();
+ return localAddress;
}
/**
- * Gets the local port of this server socket or {@code -1} if the socket is
- * unbound.
+ * Gets the local port of this server socket or {@code -1} if the socket is not bound.
+ * If the socket has ever been bound this method will return the local port it was bound to,
+ * even after it has been closed.
*
* @return the local port this server is listening on.
*/
@@ -300,9 +313,12 @@ public class ServerSocket implements Closeable {
if (isBound()) {
throw new BindException("Socket is already bound");
}
- int port = 0;
- InetAddress addr = Inet4Address.ANY;
- if (localAddr != null) {
+ InetAddress addr;
+ int port;
+ if (localAddr == null) {
+ addr = Inet4Address.ANY;
+ port = 0;
+ } else {
if (!(localAddr instanceof InetSocketAddress)) {
throw new IllegalArgumentException("Local address not an InetSocketAddress: " +
localAddr.getClass());
@@ -317,7 +333,7 @@ public class ServerSocket implements Closeable {
synchronized (this) {
try {
impl.bind(addr, port);
- isBound = true;
+ readBackBindState();
impl.listen(backlog > 0 ? backlog : DEFAULT_BACKLOG);
} catch (IOException e) {
close();
@@ -327,8 +343,9 @@ public class ServerSocket implements Closeable {
}
/**
- * Gets the local socket address of this server socket or {@code null} if
- * the socket is unbound. This is useful on multihomed hosts.
+ * Gets the local socket address of this server socket or {@code null} if the socket is unbound.
+ * This is useful on multihomed hosts. If the socket has ever been bound this method will return
+ * the local address it was bound to, even after it has been closed.
*
* @return the local socket address and port this socket is bound to.
*/
@@ -336,7 +353,7 @@ public class ServerSocket implements Closeable {
if (!isBound()) {
return null;
}
- return new InetSocketAddress(getInetAddress(), getLocalPort());
+ return new InetSocketAddress(localAddress, getLocalPort());
}
/**
diff --git a/luni/src/main/java/java/net/Socket.java b/luni/src/main/java/java/net/Socket.java
index 36fdf28..5dd350a 100644
--- a/luni/src/main/java/java/net/Socket.java
+++ b/luni/src/main/java/java/net/Socket.java
@@ -312,12 +312,29 @@ public class Socket implements Closeable {
*/
public synchronized void close() throws IOException {
isClosed = true;
- // RI compatibility: the RI returns the any address (but the original local port) after close.
+ isConnected = false;
+ // RI compatibility: the RI returns the any address (but the original local port) after
+ // close.
localAddress = Inet4Address.ANY;
impl.close();
}
/**
+ * Sets the Socket and its related SocketImpl state as if a successful close() took place,
+ * without actually performing an OS close().
+ *
+ * @hide used in java.nio
+ */
+ public void onClose() {
+ isClosed = true;
+ isConnected = false;
+ // RI compatibility: the RI returns the any address (but the original local port) after
+ // close.
+ localAddress = Inet4Address.ANY;
+ impl.onClose();
+ }
+
+ /**
* Returns the IP address of the target host this socket is connected to, or null if this
* socket is not yet connected.
*/
@@ -329,7 +346,9 @@ public class Socket implements Closeable {
}
/**
- * Returns an input stream to read data from this socket.
+ * Returns an input stream to read data from this socket. If the socket has an associated
+ * {@link SocketChannel} and that channel is in non-blocking mode then reads from the
+ * stream will throw a {@link java.nio.channels.IllegalBlockingModeException}.
*
* @return the byte-oriented input stream.
* @throws IOException
@@ -353,15 +372,16 @@ public class Socket implements Closeable {
}
/**
- * Returns the local IP address this socket is bound to, or {@code InetAddress.ANY} if
- * the socket is unbound.
+ * Returns the local IP address this socket is bound to, or an address for which
+ * {@link InetAddress#isAnyLocalAddress()} returns true if the socket is closed or unbound.
*/
public InetAddress getLocalAddress() {
return localAddress;
}
/**
- * Returns the local port this socket is bound to, or -1 if the socket is unbound.
+ * Returns the local port this socket is bound to, or -1 if the socket is unbound. If the socket
+ * has been closed this method will still return the local port the socket was bound to.
*/
public int getLocalPort() {
if (!isBound()) {
@@ -371,7 +391,9 @@ public class Socket implements Closeable {
}
/**
- * Returns an output stream to write data into this socket.
+ * Returns an output stream to write data into this socket. If the socket has an associated
+ * {@link SocketChannel} and that channel is in non-blocking mode then writes to the
+ * stream will throw a {@link java.nio.channels.IllegalBlockingModeException}.
*
* @return the byte-oriented output stream.
* @throws IOException
@@ -564,6 +586,7 @@ public class Socket implements Closeable {
impl.bind(addr, localPort);
}
isBound = true;
+ cacheLocalAddress();
impl.connect(dstAddress, dstPort);
isConnected = true;
cacheLocalAddress();
@@ -672,9 +695,10 @@ public class Socket implements Closeable {
}
/**
- * Returns the local address and port of this socket as a SocketAddress or
- * null if the socket is unbound. This is useful on multihomed
- * hosts.
+ * Returns the local address and port of this socket as a SocketAddress or null if the socket
+ * has never been bound. If the socket is closed but has previously been bound then an address
+ * for which {@link InetAddress#isAnyLocalAddress()} returns true will be returned with the
+ * previously-bound port. This is useful on multihomed hosts.
*/
public SocketAddress getLocalSocketAddress() {
if (!isBound()) {
@@ -744,9 +768,12 @@ public class Socket implements Closeable {
throw new BindException("Socket is already bound");
}
- int port = 0;
- InetAddress addr = Inet4Address.ANY;
- if (localAddr != null) {
+ int port;
+ InetAddress addr;
+ if (localAddr == null) {
+ port = 0;
+ addr = Inet4Address.ANY;
+ } else {
if (!(localAddr instanceof InetSocketAddress)) {
throw new IllegalArgumentException("Local address not an InetSocketAddress: " +
localAddr.getClass());
@@ -771,6 +798,18 @@ public class Socket implements Closeable {
}
/**
+ * Sets the Socket and its related SocketImpl state as if a successful bind() took place,
+ * without actually performing an OS bind().
+ *
+ * @hide used in java.nio
+ */
+ public void onBind(InetAddress localAddress, int localPort) {
+ isBound = true;
+ this.localAddress = localAddress;
+ impl.onBind(localAddress, localPort);
+ }
+
+ /**
* Connects this socket to the given remote host address and port specified
* by the SocketAddress {@code remoteAddr}.
*
@@ -851,6 +890,17 @@ public class Socket implements Closeable {
}
/**
+ * Sets the Socket and its related SocketImpl state as if a successful connect() took place,
+ * without actually performing an OS connect().
+ *
+ * @hide internal use only
+ */
+ public void onConnect(InetAddress remoteAddress, int remotePort) {
+ isConnected = true;
+ impl.onConnect(remoteAddress, remotePort);
+ }
+
+ /**
* Returns whether the incoming channel of the socket has already been
* closed.
*
diff --git a/luni/src/main/java/java/net/SocketImpl.java b/luni/src/main/java/java/net/SocketImpl.java
index 92de9cf..bd36ec7 100644
--- a/luni/src/main/java/java/net/SocketImpl.java
+++ b/luni/src/main/java/java/net/SocketImpl.java
@@ -294,4 +294,31 @@ public abstract class SocketImpl implements SocketOptions {
*/
protected void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
}
+
+ /**
+ * Initialize the bind() state.
+ * @hide used in java.nio.
+ */
+ public void onBind(InetAddress localAddress, int localPort) {
+ // Do not add any code to these methods. They are concrete only to preserve API
+ // compatibility.
+ }
+
+ /**
+ * Initialize the connect() state.
+ * @hide used in java.nio.
+ */
+ public void onConnect(InetAddress remoteAddress, int remotePort) {
+ // Do not add any code to these methods. They are concrete only to preserve API
+ // compatibility.
+ }
+
+ /**
+ * Initialize the close() state.
+ * @hide used in java.nio.
+ */
+ public void onClose() {
+ // Do not add any code to these methods. They are concrete only to preserve API
+ // compatibility.
+ }
}
diff --git a/luni/src/main/java/java/net/SocketOptions.java b/luni/src/main/java/java/net/SocketOptions.java
index e23fc97..d0df689 100644
--- a/luni/src/main/java/java/net/SocketOptions.java
+++ b/luni/src/main/java/java/net/SocketOptions.java
@@ -28,19 +28,27 @@ package java.net;
*/
public interface SocketOptions {
/**
- * Number of seconds to wait when closing a socket if there
- * is still some buffered data to be sent.
+ * Number of seconds to wait when closing a socket if there is still some buffered data to be
+ * sent.
*
- * <p>If this option is set to 0, the TCP socket is closed forcefully and the
- * call to {@code close} returns immediately.
+ * <p>The option can be set to disabled using {@link #setOption(int, Object)} with a value of
+ * {@code Boolean.FALSE}.
*
- * <p>If this option is set to a value greater than 0, the value is interpreted
- * as the number of seconds to wait. If all data could be sent
- * during this time, the socket is closed normally. Otherwise the connection will be
- * closed forcefully.
+ * <p>If this option is set to 0, the TCP socket is closed forcefully and the call to
+ * {@code close} returns immediately.
*
- * <p>Valid values for this option are in the range 0 to 65535 inclusive. (Larger
+ * If this option is disabled, closing a socket will return immediately and the close will be
+ * handled in the background.
+ *
+ * <p>If this option is set to a value greater than 0, the value is interpreted as the number of
+ * seconds to wait. If all data could be sent during this time, the socket is closed normally.
+ * Otherwise the connection will be closed forcefully.
+ *
+ * <p>Valid numeric values for this option are in the range 0 to 65535 inclusive. (Larger
* timeouts will be treated as 65535s timeouts; roughly 18 hours.)
+ *
+ * <p>This option is intended for use with sockets in blocking mode. The behavior of this option
+ * for non-blocking sockets is undefined.
*/
public static final int SO_LINGER = 128;
@@ -54,16 +62,21 @@ public interface SocketOptions {
public static final int SO_TIMEOUT = 4102;
/**
- * This boolean option specifies whether data is sent immediately on this socket.
- * As a side-effect this could lead to low packet efficiency. The
- * socket implementation uses the Nagle's algorithm to try to reach a higher
- * packet efficiency if this option is disabled.
+ * This boolean option specifies whether data is sent immediately on this socket or buffered.
+ * <p>
+ * If set to {@code Boolean.TRUE} the Nagle algorithm is disabled and there is no buffering.
+ * This could lead to low packet efficiency. When set to {@code Boolean.FALSE} the the socket
+ * implementation uses buffering to try to reach a higher packet efficiency.
+ *
+ * <p>See <a href="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122: Requirements for Internet
+ * Hosts -- Communication Layers</a> for more information about buffering and the Nagle
+ * algorithm.
*/
public static final int TCP_NODELAY = 1;
/**
* This is an IPv4-only socket option whose functionality is subsumed by
- * {@link #IP_MULTICAST_IF} and not implemented on Android.
+ * {@link #IP_MULTICAST_IF2} and not implemented on Android.
*/
public static final int IP_MULTICAST_IF = 16;
@@ -73,9 +86,18 @@ public interface SocketOptions {
public static final int SO_BINDADDR = 15;
/**
- * This boolean option specifies whether a reuse of a local address is allowed even
- * if another socket is not yet removed by the operating system. It's only
- * available on a {@code MulticastSocket}.
+ * This boolean option specifies whether a reuse of a local address is allowed when another
+ * socket has not yet been removed by the operating system.
+ *
+ * <p>For connection-oriented sockets, if this option is disabled and if there is another socket
+ * in state TIME_WAIT on a given address then another socket binding to that address would fail.
+ * Setting this value after a socket is bound has no effect.
+ *
+ * <p>For datagram sockets this option determines whether several sockets can listen on the
+ * same address; when enabled each socket will receive a copy of the datagram.
+ *
+ * <p>See <a href="https://www.ietf.org/rfc/rfc793.txt">RFC 793: Transmission Control Protocol
+ * </a> for more information about socket re-use.
*/
public static final int SO_REUSEADDR = 4;
@@ -93,11 +115,18 @@ public interface SocketOptions {
* This is a hint to the kernel; the kernel may use a larger buffer.
*
* <p>For datagram sockets, packets larger than this value will be discarded.
+ *
+ * <p>See <a href="http://www.ietf.org/rfc/rfc1323.txt">RFC1323: TCP Extensions for High
+ * Performance</a> for more information about TCP/IP buffering.
*/
public static final int SO_RCVBUF = 4098;
/**
- * This boolean option specifies whether the kernel sends keepalive messages.
+ * This boolean option specifies whether the kernel sends keepalive messages on
+ * connection-oriented sockets.
+ *
+ * <p>See <a href="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122: Requirements for Internet
+ * Hosts -- Communication Layers</a> for more information on keep-alive.
*/
public static final int SO_KEEPALIVE = 8;
@@ -114,15 +143,18 @@ public interface SocketOptions {
/**
* This boolean option specifies whether the local loopback of multicast packets is
- * enabled or disabled. This option is enabled by default on multicast
- * sockets. Note that the sense of this option in Java is the
- * <i>opposite</i> of the underlying Unix {@code IP_MULTICAST_LOOP}.
- * See {@link MulticastSocket#setLoopbackMode}.
+ * enabled or disabled. This loopback is enabled by default on multicast sockets.
+ *
+ * <p>See <a href="http://tools.ietf.org/rfc/rfc1112.txt">RFC 1112: Host Extensions for IP
+ * Multicasting</a> for more information about IP multicast.
+ *
+ * <p>See {@link MulticastSocket#setLoopbackMode}.
*/
public static final int IP_MULTICAST_LOOP = 18;
/**
- * This boolean option can be used to enable broadcasting on datagram sockets.
+ * This boolean option can be used to enable or disable broadcasting on datagram sockets. This
+ * option must be enabled to send broadcast messages. The default value is false.
*/
public static final int SO_BROADCAST = 32;
@@ -135,6 +167,9 @@ public interface SocketOptions {
/**
* This integer option sets the outgoing interface for multicast packets
* using an interface index.
+ *
+ * <p>See <a href="http://tools.ietf.org/rfc/rfc1112.txt">RFC 1112: Host Extensions for IP
+ * Multicasting</a> for more information about IP multicast.
*/
public static final int IP_MULTICAST_IF2 = 31;
diff --git a/luni/src/main/java/java/net/URI.java b/luni/src/main/java/java/net/URI.java
index 6b7b1da..f206473 100644
--- a/luni/src/main/java/java/net/URI.java
+++ b/luni/src/main/java/java/net/URI.java
@@ -461,11 +461,14 @@ public final class URI implements Comparable<URI>, Serializable {
if (index < (temp.length() - 1)) { // port part is not empty
try {
- tempPort = Integer.parseInt(temp.substring(index + 1));
- if (tempPort < 0) {
+ char firstPortChar = temp.charAt(index + 1);
+ if (firstPortChar >= '0' && firstPortChar <= '9') {
+ // allow only digits, no signs
+ tempPort = Integer.parseInt(temp.substring(index + 1));
+ } else {
if (forceServer) {
throw new URISyntaxException(authority,
- "Invalid port number", hostIndex + index + 1);
+ "Invalid port number", hostIndex + index + 1);
}
return;
}
@@ -766,33 +769,51 @@ public final class URI implements Comparable<URI>, Serializable {
}
/**
- * Returns true if {@code first} and {@code second} are equal after
- * unescaping hex sequences like %F1 and %2b.
+ * Returns true if the given URI escaped strings {@code first} and {@code second} are
+ * equal.
+ *
+ * TODO: This method assumes that both strings are escaped using the same escape rules
+ * yet it still performs case insensitive comparison of the escaped sequences.
+ * Why is this necessary ? We can just replace it with first.equals(second)
+ * otherwise.
*/
private boolean escapedEquals(String first, String second) {
- if (first.indexOf('%') != second.indexOf('%')) {
- return first.equals(second);
+ // This length test isn't a micro-optimization. We need it because we sometimes
+ // calculate the number of characters to match based on the length of the second
+ // string. If the second string is shorter than the first, we might attempt to match
+ // 0 chars, and regionMatches is specified to return true in that case.
+ if (first.length() != second.length()) {
+ return false;
}
- int index, prevIndex = 0;
- while ((index = first.indexOf('%', prevIndex)) != -1
- && second.indexOf('%', prevIndex) == index) {
- boolean match = first.substring(prevIndex, index).equals(
- second.substring(prevIndex, index));
- if (!match) {
+ int prevIndex = 0;
+ while (true) {
+ int index = first.indexOf('%', prevIndex);
+ int index1 = second.indexOf('%', prevIndex);
+ if (index != index1) {
+ return false;
+ }
+
+ // index == index1 from this point on.
+
+ if (index == -1) {
+ // No more escapes, match the remainder of the string
+ // normally.
+ return first.regionMatches(prevIndex, second, prevIndex,
+ second.length() - prevIndex);
+ }
+
+ if (!first.regionMatches(prevIndex, second, prevIndex, (index - prevIndex))) {
return false;
}
- match = first.substring(index + 1, index + 3).equalsIgnoreCase(
- second.substring(index + 1, index + 3));
- if (!match) {
+ if (!first.regionMatches(true /* ignore case */, index + 1, second, index + 1, 2)) {
return false;
}
index += 3;
prevIndex = index;
}
- return first.substring(prevIndex).equals(second.substring(prevIndex));
}
@Override public boolean equals(Object o) {
diff --git a/luni/src/main/java/java/net/URLConnection.java b/luni/src/main/java/java/net/URLConnection.java
index 74c15ce..2fb3f45 100644
--- a/luni/src/main/java/java/net/URLConnection.java
+++ b/luni/src/main/java/java/net/URLConnection.java
@@ -308,9 +308,8 @@ public abstract class URLConnection {
/**
* Returns the content length in bytes specified by the response header field
- * {@code content-length} or {@code -1} if this field is not set.
- *
- * @return the value of the response header field {@code content-length}.
+ * {@code content-length} or {@code -1} if this field is not set or cannot be represented as an
+ * {@code int}.
*/
public int getContentLength() {
return getHeaderFieldInt("Content-Length", -1);
@@ -531,7 +530,7 @@ public abstract class URLConnection {
/**
* Returns the specified header value as a number. Returns the {@code
* defaultValue} if no such header field could be found or the value could
- * not be parsed as an {@code Integer}.
+ * not be parsed as an {@code int}.
*
* @param field
* the header field name whose value is needed.
diff --git a/luni/src/main/java/java/net/URLStreamHandler.java b/luni/src/main/java/java/net/URLStreamHandler.java
index 8a6c264..d21bb9c 100644
--- a/luni/src/main/java/java/net/URLStreamHandler.java
+++ b/luni/src/main/java/java/net/URLStreamHandler.java
@@ -131,9 +131,11 @@ public abstract class URLStreamHandler {
host = spec.substring(hostStart, hostEnd);
int portStart = hostEnd + 1;
if (portStart < fileStart) {
- port = Integer.parseInt(spec.substring(portStart, fileStart));
- if (port < 0) {
- throw new IllegalArgumentException("port < 0: " + port);
+ char firstPortChar = spec.charAt(portStart);
+ if (firstPortChar >= '0' && firstPortChar <= '9') {
+ port = Integer.parseInt(spec.substring(portStart, fileStart));
+ } else {
+ throw new IllegalArgumentException("invalid port: " + port);
}
}
path = null;
diff --git a/luni/src/main/java/java/nio/Buffer.java b/luni/src/main/java/java/nio/Buffer.java
index 9b7be52..53ad171 100644
--- a/luni/src/main/java/java/nio/Buffer.java
+++ b/luni/src/main/java/java/nio/Buffer.java
@@ -85,22 +85,16 @@ public abstract class Buffer {
/**
* For direct buffers, the effective address of the data; zero otherwise.
* This is set in the constructor.
- * TODO: make this final at the cost of loads of extra constructors? [how many?]
*/
- long effectiveDirectAddress;
+ final long effectiveDirectAddress;
- /**
- * For direct buffers, the underlying MemoryBlock; null otherwise.
- */
- final MemoryBlock block;
-
- Buffer(int elementSizeShift, int capacity, MemoryBlock block) {
+ Buffer(int elementSizeShift, int capacity, long effectiveDirectAddress) {
this._elementSizeShift = elementSizeShift;
if (capacity < 0) {
throw new IllegalArgumentException("capacity < 0: " + capacity);
}
this.capacity = this.limit = capacity;
- this.block = block;
+ this.effectiveDirectAddress = effectiveDirectAddress;
}
/**
@@ -296,7 +290,7 @@ public abstract class Buffer {
* the new limit, must not be negative and not greater than
* capacity.
* @return this buffer.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* if <code>newLimit</code> is invalid.
*/
public final Buffer limit(int newLimit) {
@@ -344,7 +338,7 @@ public abstract class Buffer {
* the new position, must be not negative and not greater than
* limit.
* @return this buffer.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* if <code>newPosition</code> is invalid.
*/
public final Buffer position(int newPosition) {
@@ -377,7 +371,7 @@ public abstract class Buffer {
* Resets the position of this buffer to the <code>mark</code>.
*
* @return this buffer.
- * @exception InvalidMarkException
+ * @throws InvalidMarkException
* if the mark is not set.
*/
public final Buffer reset() {
@@ -409,4 +403,9 @@ public abstract class Buffer {
return getClass().getName() +
"[position=" + position + ",limit=" + limit + ",capacity=" + capacity + "]";
}
+
+ /** @hide for testing only */
+ public final int getElementSizeShift() {
+ return _elementSizeShift;
+ }
}
diff --git a/luni/src/main/java/java/nio/ByteArrayBuffer.java b/luni/src/main/java/java/nio/ByteArrayBuffer.java
index e8d7ecc..6a273ed 100644
--- a/luni/src/main/java/java/nio/ByteArrayBuffer.java
+++ b/luni/src/main/java/java/nio/ByteArrayBuffer.java
@@ -38,7 +38,7 @@ final class ByteArrayBuffer extends ByteBuffer {
}
private ByteArrayBuffer(int capacity, byte[] backingArray, int arrayOffset, boolean isReadOnly) {
- super(capacity, null);
+ super(capacity, 0);
this.backingArray = backingArray;
this.arrayOffset = arrayOffset;
this.isReadOnly = isReadOnly;
diff --git a/luni/src/main/java/java/nio/ByteBuffer.java b/luni/src/main/java/java/nio/ByteBuffer.java
index 456a309..61093fa 100644
--- a/luni/src/main/java/java/nio/ByteBuffer.java
+++ b/luni/src/main/java/java/nio/ByteBuffer.java
@@ -69,7 +69,11 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
if (capacity < 0) {
throw new IllegalArgumentException("capacity < 0: " + capacity);
}
- return new DirectByteBuffer(MemoryBlock.allocate(capacity), capacity, 0, false, null);
+ // Ensure alignment by 8.
+ MemoryBlock memoryBlock = MemoryBlock.allocate(capacity + 7);
+ long address = memoryBlock.toLong();
+ long alignedAddress = (address + 7) & ~(long)7;
+ return new DirectByteBuffer(memoryBlock, capacity, (int)(alignedAddress - address), false, null);
}
/**
@@ -101,7 +105,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* the length, must not be negative and not greater than
* {@code array.length - start}.
* @return the created byte buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code byteCount} is invalid.
*/
public static ByteBuffer wrap(byte[] array, int start, int byteCount) {
@@ -112,17 +116,17 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
return buf;
}
- ByteBuffer(int capacity, MemoryBlock block) {
- super(0, capacity, block);
+ ByteBuffer(int capacity, long effectiveDirectAddress) {
+ super(0, capacity, effectiveDirectAddress);
}
/**
* Returns the byte array which this buffer is based on, if there is one.
*
* @return the byte array which this buffer is based on.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if this buffer is based on a read-only array.
- * @exception UnsupportedOperationException
+ * @throws UnsupportedOperationException
* if this buffer is not based on an array.
*/
@Override public final byte[] array() {
@@ -137,9 +141,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* position of the buffer.
*
* @return the offset of the byte array which this buffer is based on.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if this buffer is based on a read-only array.
- * @exception UnsupportedOperationException
+ * @throws UnsupportedOperationException
* if this buffer is not based on an array.
*/
@Override public final int arrayOffset() {
@@ -260,7 +264,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* cleared.
*
* @return {@code this}
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer compact();
@@ -274,7 +278,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @return a negative value if this is less than {@code other}; 0 if this
* equals to {@code other}; a positive value if this is greater
* than {@code other}.
- * @exception ClassCastException
+ * @throws ClassCastException
* if {@code other} is not a byte buffer.
*/
@Override public int compareTo(ByteBuffer otherBuffer) {
@@ -350,7 +354,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* Returns the byte at the current position and increases the position by 1.
*
* @return the byte at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is equal or greater than limit.
*/
public abstract byte get();
@@ -365,7 +369,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param dst
* the destination byte array.
* @return {@code this}
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code dst.length} is greater than {@code remaining()}.
*/
public ByteBuffer get(byte[] dst) {
@@ -386,8 +390,8 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* the number of bytes to read, must not be negative and not
* greater than {@code dst.length - dstOffset}
* @return {@code this}
- * @exception IndexOutOfBoundsException if {@code dstOffset < 0 || byteCount < 0}
- * @exception BufferUnderflowException if {@code byteCount > remaining()}
+ * @throws IndexOutOfBoundsException if {@code dstOffset < 0 || byteCount < 0}
+ * @throws BufferUnderflowException if {@code byteCount > remaining()}
*/
public ByteBuffer get(byte[] dst, int dstOffset, int byteCount) {
Arrays.checkOffsetAndCount(dst.length, dstOffset, byteCount);
@@ -406,7 +410,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param index
* the index, must not be negative and less than limit.
* @return the byte at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
*/
public abstract byte get(int index);
@@ -418,7 +422,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* according to the current byte order and returned.
*
* @return the char at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is greater than {@code limit - 2}.
*/
public abstract char getChar();
@@ -434,7 +438,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* the index, must not be negative and equal or less than
* {@code limit - 2}.
* @return the char at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
*/
public abstract char getChar(int index);
@@ -447,7 +451,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* according to the current byte order and returned.
*
* @return the double at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is greater than {@code limit - 8}.
*/
public abstract double getDouble();
@@ -463,7 +467,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* the index, must not be negative and equal or less than
* {@code limit - 8}.
* @return the double at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
*/
public abstract double getDouble(int index);
@@ -476,7 +480,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* according to the current byte order and returned.
*
* @return the float at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is greater than {@code limit - 4}.
*/
public abstract float getFloat();
@@ -492,7 +496,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* the index, must not be negative and equal or less than
* {@code limit - 4}.
* @return the float at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
*/
public abstract float getFloat(int index);
@@ -504,7 +508,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* according to the current byte order and returned.
*
* @return the int at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is greater than {@code limit - 4}.
*/
public abstract int getInt();
@@ -520,7 +524,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* the index, must not be negative and equal or less than
* {@code limit - 4}.
* @return the int at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
*/
public abstract int getInt(int index);
@@ -532,7 +536,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* according to the current byte order and returned.
*
* @return the long at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is greater than {@code limit - 8}.
*/
public abstract long getLong();
@@ -548,7 +552,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* the index, must not be negative and equal or less than
* {@code limit - 8}.
* @return the long at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
*/
public abstract long getLong(int index);
@@ -560,7 +564,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* according to the current byte order and returned.
*
* @return the short at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is greater than {@code limit - 2}.
*/
public abstract short getShort();
@@ -576,7 +580,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* the index, must not be negative and equal or less than
* {@code limit - 2}.
* @return the short at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
*/
public abstract short getShort(int index);
@@ -609,6 +613,30 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
@Override public abstract boolean isDirect();
/**
+ * Indicates whether this buffer is still accessible.
+ *
+ * @return {@code true} if this buffer is accessible, {@code false} if the
+ * buffer was made inaccessible (e.g. freed) and should not be used.
+ * @hide
+ */
+ public boolean isAccessible() {
+ return true;
+ }
+
+ /**
+ * Sets buffer accessibility (only supported for direct byte buffers). If
+ * {@code accessible} is {@code false}, {@link #isAccessible} will return
+ * false, and any attempt to access the buffer will throw an exception. If
+ * {@code true}, the buffer will become useable again, unless it has been
+ * freed.
+ *
+ * @hide
+ */
+ public void setAccessible(boolean accessible) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Returns the byte order used by this buffer when converting bytes from/to
* other primitive types.
* <p>
@@ -667,9 +695,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param b
* the byte to write.
* @return {@code this}
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is equal or greater than limit.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer put(byte b);
@@ -684,9 +712,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param src
* the source byte array.
* @return {@code this}
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code src.length}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public final ByteBuffer put(byte[] src) {
@@ -707,11 +735,11 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* the number of bytes to write, must not be negative and not
* greater than {@code src.length - srcOffset}.
* @return {@code this}
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code byteCount}.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code srcOffset} or {@code byteCount} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public ByteBuffer put(byte[] src, int srcOffset, int byteCount) {
@@ -733,21 +761,27 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param src
* the source byte buffer.
* @return {@code this}
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code src.remaining()} is greater than this buffer's
* {@code remaining()}.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* if {@code src} is this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public ByteBuffer put(ByteBuffer src) {
+ if (!isAccessible()) {
+ throw new IllegalStateException("buffer is inaccessible");
+ }
if (isReadOnly()) {
throw new ReadOnlyBufferException();
}
if (src == this) {
throw new IllegalArgumentException("src == this");
}
+ if (!src.isAccessible()) {
+ throw new IllegalStateException("src buffer is inaccessible");
+ }
int srcByteCount = src.remaining();
if (srcByteCount > remaining()) {
throw new BufferOverflowException();
@@ -782,9 +816,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param b
* the byte to write.
* @return {@code this}
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer put(int index, byte b);
@@ -798,9 +832,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the char to write.
* @return {@code this}
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is greater than {@code limit - 2}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putChar(char value);
@@ -817,9 +851,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the char to write.
* @return {@code this}
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putChar(int index, char value);
@@ -833,9 +867,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the double to write.
* @return {@code this}
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is greater than {@code limit - 8}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putDouble(double value);
@@ -852,9 +886,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the double to write.
* @return {@code this}
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putDouble(int index, double value);
@@ -868,9 +902,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the float to write.
* @return {@code this}
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is greater than {@code limit - 4}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putFloat(float value);
@@ -887,9 +921,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the float to write.
* @return {@code this}
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putFloat(int index, float value);
@@ -903,9 +937,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the int to write.
* @return {@code this}
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is greater than {@code limit - 4}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putInt(int value);
@@ -922,9 +956,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the int to write.
* @return {@code this}
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putInt(int index, int value);
@@ -938,9 +972,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the long to write.
* @return {@code this}
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is greater than {@code limit - 8}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putLong(long value);
@@ -957,9 +991,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the long to write.
* @return {@code this}
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putLong(int index, long value);
@@ -973,9 +1007,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the short to write.
* @return {@code this}
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is greater than {@code limit - 2}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putShort(short value);
@@ -992,9 +1026,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer
* @param value
* the short to write.
* @return {@code this}
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if {@code index} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ByteBuffer putShort(int index, short value);
diff --git a/luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java
index 16d0688..9d06cce 100644
--- a/luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java
@@ -42,10 +42,9 @@ final class ByteBufferAsCharBuffer extends CharBuffer {
}
private ByteBufferAsCharBuffer(ByteBuffer byteBuffer) {
- super(byteBuffer.capacity() / SizeOf.CHAR);
+ super(byteBuffer.capacity() / SizeOf.CHAR, byteBuffer.effectiveDirectAddress);
this.byteBuffer = byteBuffer;
this.byteBuffer.clear();
- this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress;
}
@Override
diff --git a/luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java
index 044bf59..8271daf 100644
--- a/luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java
@@ -42,10 +42,9 @@ final class ByteBufferAsDoubleBuffer extends DoubleBuffer {
}
private ByteBufferAsDoubleBuffer(ByteBuffer byteBuffer) {
- super(byteBuffer.capacity() / SizeOf.DOUBLE);
+ super(byteBuffer.capacity() / SizeOf.DOUBLE, byteBuffer.effectiveDirectAddress);
this.byteBuffer = byteBuffer;
this.byteBuffer.clear();
- this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress;
}
@Override
diff --git a/luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java
index a67affe..eaf2b8f 100644
--- a/luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java
@@ -41,10 +41,9 @@ final class ByteBufferAsFloatBuffer extends FloatBuffer {
}
ByteBufferAsFloatBuffer(ByteBuffer byteBuffer) {
- super(byteBuffer.capacity() / SizeOf.FLOAT);
+ super(byteBuffer.capacity() / SizeOf.FLOAT, byteBuffer.effectiveDirectAddress);
this.byteBuffer = byteBuffer;
this.byteBuffer.clear();
- this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress;
}
@Override
diff --git a/luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java
index a3211b8..f584f1a 100644
--- a/luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java
@@ -42,10 +42,9 @@ final class ByteBufferAsIntBuffer extends IntBuffer {
}
private ByteBufferAsIntBuffer(ByteBuffer byteBuffer) {
- super(byteBuffer.capacity() / SizeOf.INT);
+ super(byteBuffer.capacity() / SizeOf.INT, byteBuffer.effectiveDirectAddress);
this.byteBuffer = byteBuffer;
this.byteBuffer.clear();
- this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress;
}
@Override
diff --git a/luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java
index 550c675..8afa66e 100644
--- a/luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java
@@ -42,10 +42,9 @@ final class ByteBufferAsLongBuffer extends LongBuffer {
}
private ByteBufferAsLongBuffer(ByteBuffer byteBuffer) {
- super(byteBuffer.capacity() / SizeOf.LONG);
+ super(byteBuffer.capacity() / SizeOf.LONG, byteBuffer.effectiveDirectAddress);
this.byteBuffer = byteBuffer;
this.byteBuffer.clear();
- this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress;
}
@Override
diff --git a/luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java
index ff81409..52afc0e 100644
--- a/luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java
@@ -41,10 +41,9 @@ final class ByteBufferAsShortBuffer extends ShortBuffer {
}
private ByteBufferAsShortBuffer(ByteBuffer byteBuffer) {
- super(byteBuffer.capacity() / SizeOf.SHORT);
+ super(byteBuffer.capacity() / SizeOf.SHORT, byteBuffer.effectiveDirectAddress);
this.byteBuffer = byteBuffer;
this.byteBuffer.clear();
- this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress;
}
@Override
diff --git a/luni/src/main/java/java/nio/CharArrayBuffer.java b/luni/src/main/java/java/nio/CharArrayBuffer.java
index 245a799..8f9fd9b 100644
--- a/luni/src/main/java/java/nio/CharArrayBuffer.java
+++ b/luni/src/main/java/java/nio/CharArrayBuffer.java
@@ -33,7 +33,7 @@ final class CharArrayBuffer extends CharBuffer {
}
private CharArrayBuffer(int capacity, char[] backingArray, int arrayOffset, boolean isReadOnly) {
- super(capacity);
+ super(capacity, 0);
this.backingArray = backingArray;
this.arrayOffset = arrayOffset;
this.isReadOnly = isReadOnly;
diff --git a/luni/src/main/java/java/nio/CharBuffer.java b/luni/src/main/java/java/nio/CharBuffer.java
index 92cab01..748a787 100644
--- a/luni/src/main/java/java/nio/CharBuffer.java
+++ b/luni/src/main/java/java/nio/CharBuffer.java
@@ -83,7 +83,7 @@ public abstract class CharBuffer extends Buffer implements
* the length, must not be negative and not greater than
* {@code array.length - start}.
* @return the created char buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code charCount} is invalid.
*/
public static CharBuffer wrap(char[] array, int start, int charCount) {
@@ -124,7 +124,7 @@ public abstract class CharBuffer extends Buffer implements
* the end index, must be no less than {@code start} and no
* greater than {@code cs.length()}.
* @return the created char buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code end} is invalid.
*/
public static CharBuffer wrap(CharSequence cs, int start, int end) {
@@ -137,8 +137,8 @@ public abstract class CharBuffer extends Buffer implements
return result;
}
- CharBuffer(int capacity) {
- super(1, capacity, null);
+ CharBuffer(int capacity, long effectiveDirectAddress) {
+ super(1, capacity, effectiveDirectAddress);
}
public final char[] array() {
@@ -165,17 +165,8 @@ public abstract class CharBuffer extends Buffer implements
public abstract CharBuffer asReadOnlyBuffer();
/**
- * Returns the character located at the specified index in the buffer. The
- * index value is referenced from the current buffer position.
- *
- * @param index
- * the index referenced from the current buffer position. It must
- * not be less than zero but less than the value obtained from a
- * call to {@code remaining()}.
- * @return the character located at the specified index (referenced from the
- * current position) in the buffer.
- * @exception IndexOutOfBoundsException
- * if the index is invalid.
+ * Returns the character located at the given offset <i>relative to the current position</i>.
+ * @throws IndexOutOfBoundsException if {@code index < 0} or {@code index >= remaining()}.
*/
public final char charAt(int index) {
if (index < 0 || index >= remaining()) {
@@ -192,7 +183,7 @@ public abstract class CharBuffer extends Buffer implements
* {@code remaining()}; the limit is set to capacity; the mark is cleared.
*
* @return this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract CharBuffer compact();
@@ -206,7 +197,7 @@ public abstract class CharBuffer extends Buffer implements
* @return a negative value if this is less than {@code otherBuffer}; 0 if
* this equals to {@code otherBuffer}; a positive value if this is
* greater than {@code otherBuffer}.
- * @exception ClassCastException
+ * @throws ClassCastException
* if {@code otherBuffer} is not a char buffer.
*/
public int compareTo(CharBuffer otherBuffer) {
@@ -278,7 +269,7 @@ public abstract class CharBuffer extends Buffer implements
* Returns the char at the current position and increases the position by 1.
*
* @return the char at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is equal or greater than limit.
*/
public abstract char get();
@@ -293,7 +284,7 @@ public abstract class CharBuffer extends Buffer implements
* @param dst
* the destination char array.
* @return this buffer.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code dst.length} is greater than {@code remaining()}.
*/
public CharBuffer get(char[] dst) {
@@ -314,9 +305,9 @@ public abstract class CharBuffer extends Buffer implements
* The number of chars to read, must be no less than zero and no
* greater than {@code dst.length - dstOffset}.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code dstOffset} or {@code charCount} is invalid.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code charCount} is greater than {@code remaining()}.
*/
public CharBuffer get(char[] dst, int dstOffset, int charCount) {
@@ -336,7 +327,7 @@ public abstract class CharBuffer extends Buffer implements
* @param index
* the index, must not be negative and less than limit.
* @return a char at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
*/
public abstract char get(int index);
@@ -422,9 +413,9 @@ public abstract class CharBuffer extends Buffer implements
* @param c
* the char to write.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is equal or greater than limit.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract CharBuffer put(char c);
@@ -439,9 +430,9 @@ public abstract class CharBuffer extends Buffer implements
* @param src
* the source char array.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code src.length}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public final CharBuffer put(char[] src) {
@@ -462,11 +453,11 @@ public abstract class CharBuffer extends Buffer implements
* the number of chars to write, must be no less than zero and no
* greater than {@code src.length - srcOffset}.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code charCount}.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code srcOffset} or {@code charCount} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public CharBuffer put(char[] src, int srcOffset, int charCount) {
@@ -488,12 +479,12 @@ public abstract class CharBuffer extends Buffer implements
* @param src
* the source char buffer.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code src.remaining()} is greater than this buffer's
* {@code remaining()}.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* if {@code src} is this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public CharBuffer put(CharBuffer src) {
@@ -522,9 +513,9 @@ public abstract class CharBuffer extends Buffer implements
* @param c
* the char to write.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract CharBuffer put(int index, char c);
@@ -539,9 +530,9 @@ public abstract class CharBuffer extends Buffer implements
* @param str
* the string to write.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than the length of string.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public final CharBuffer put(String str) {
@@ -561,11 +552,11 @@ public abstract class CharBuffer extends Buffer implements
* the last char to write (excluding), must be less than
* {@code start} and not greater than {@code str.length()}.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code end - start}.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code end} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public CharBuffer put(String str, int start, int end) {
@@ -625,7 +616,7 @@ public abstract class CharBuffer extends Buffer implements
* {@code remaining()}.
* @return a new char buffer represents a sub-sequence of this buffer's
* current remaining content.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code end} is invalid.
*/
public abstract CharBuffer subSequence(int start, int end);
@@ -649,9 +640,9 @@ public abstract class CharBuffer extends Buffer implements
* @param c
* the char to write.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is equal or greater than limit.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public CharBuffer append(char c) {
@@ -670,9 +661,9 @@ public abstract class CharBuffer extends Buffer implements
* @param csq
* the {@code CharSequence} to write.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than the length of csq.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public CharBuffer append(CharSequence csq) {
@@ -695,11 +686,11 @@ public abstract class CharBuffer extends Buffer implements
* the last char to write (excluding), must be less than
* {@code start} and not greater than {@code csq.length()}.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code end - start}.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code end} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public CharBuffer append(CharSequence csq, int start, int end) {
diff --git a/luni/src/main/java/java/nio/CharSequenceAdapter.java b/luni/src/main/java/java/nio/CharSequenceAdapter.java
index f686827..fb8a0f1 100644
--- a/luni/src/main/java/java/nio/CharSequenceAdapter.java
+++ b/luni/src/main/java/java/nio/CharSequenceAdapter.java
@@ -42,7 +42,7 @@ final class CharSequenceAdapter extends CharBuffer {
final CharSequence sequence;
CharSequenceAdapter(CharSequence chseq) {
- super(chseq.length());
+ super(chseq.length(), 0);
sequence = chseq;
}
diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java
index 39f2128..9008637 100644
--- a/luni/src/main/java/java/nio/DatagramChannelImpl.java
+++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java
@@ -17,15 +17,18 @@
package java.nio;
+import android.system.ErrnoException;
import java.io.FileDescriptor;
-import java.io.IOException;
import java.io.InterruptedIOException;
+import java.io.IOException;
import java.net.ConnectException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.DatagramSocketImpl;
+import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
import java.net.PlainDatagramSocketImpl;
import java.net.SocketAddress;
import java.net.SocketException;
@@ -35,8 +38,10 @@ import java.nio.channels.DatagramChannel;
import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.spi.SelectorProvider;
+import java.nio.channels.UnresolvedAddressException;
+import java.nio.channels.UnsupportedAddressTypeException;
import java.util.Arrays;
-import libcore.io.ErrnoException;
+import java.util.Set;
import libcore.io.IoBridge;
import libcore.io.IoUtils;
import libcore.io.Libcore;
@@ -50,10 +55,13 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
private final FileDescriptor fd;
// Our internal DatagramSocket.
- private DatagramSocket socket = null;
+ private DatagramSocket socket;
+
+ // The remote address to be connected.
+ InetSocketAddress connectAddress;
- // The address to be connected.
- InetSocketAddress connectAddress = null;
+ // The local address.
+ InetAddress localAddress;
// local port
private int localPort;
@@ -98,19 +106,38 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
}
/**
- * @see java.nio.channels.DatagramChannel#isConnected()
+ * Initialise the isBound, localAddress and localPort state from the file descriptor. Used when
+ * some or all of the bound state has been left to the OS to decide, or when the Socket handled
+ * bind() or connect().
+ *
+ * @param updateSocketState
+ * if the associated socket (if present) needs to be updated
+ * @hide used to sync state, non-private to avoid synthetic method
*/
+ void onBind(boolean updateSocketState) {
+ SocketAddress sa;
+ try {
+ sa = Libcore.os.getsockname(fd);
+ } catch (ErrnoException errnoException) {
+ throw new AssertionError(errnoException);
+ }
+ isBound = true;
+ InetSocketAddress localSocketAddress = (InetSocketAddress) sa;
+ localAddress = localSocketAddress.getAddress();
+ localPort = localSocketAddress.getPort();
+ if (updateSocketState && socket != null) {
+ socket.onBind(localAddress, localPort);
+ }
+ }
+
@Override
synchronized public boolean isConnected() {
return connected;
}
- /**
- * @see java.nio.channels.DatagramChannel#connect(java.net.SocketAddress)
- */
@Override
synchronized public DatagramChannel connect(SocketAddress address) throws IOException {
- // must open
+ // must be open
checkOpen();
// status must be un-connected.
if (connected) {
@@ -119,43 +146,71 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
// check the address
InetSocketAddress inetSocketAddress = SocketChannelImpl.validateAddress(address);
+ InetAddress remoteAddress = inetSocketAddress.getAddress();
+ int remotePort = inetSocketAddress.getPort();
try {
begin();
- IoBridge.connect(fd, inetSocketAddress.getAddress(), inetSocketAddress.getPort());
+ IoBridge.connect(fd, remoteAddress, remotePort);
} catch (ConnectException e) {
// ConnectException means connect fail, not exception
} finally {
end(true);
}
- // set the connected address.
- connectAddress = inetSocketAddress;
- connected = true;
- isBound = true;
+ // connect() performs a bind() if an explicit bind() was not performed. Keep the local
+ // address state held by the channel and the socket up to date.
+ if (!isBound) {
+ onBind(true /* updateSocketState */);
+ }
+
+ // Keep the connected state held by the channel and the socket up to date.
+ onConnect(remoteAddress, remotePort, true /* updateSocketState */);
return this;
}
/**
- * @see java.nio.channels.DatagramChannel#disconnect()
+ * Initialize the state associated with being connected, optionally syncing the socket if there
+ * is one.
+ * @hide used to sync state, non-private to avoid synthetic method
*/
+ void onConnect(InetAddress remoteAddress, int remotePort, boolean updateSocketState) {
+ connected = true;
+ connectAddress = new InetSocketAddress(remoteAddress, remotePort);
+ if (updateSocketState && socket != null) {
+ socket.onConnect(remoteAddress, remotePort);
+ }
+ }
+
@Override
synchronized public DatagramChannel disconnect() throws IOException {
if (!isConnected() || !isOpen()) {
return this;
}
- connected = false;
- connectAddress = null;
+
+ // Keep the disconnected state held by the channel and the socket up to date.
+ onDisconnect(true /* updateSocketState */);
+
try {
Libcore.os.connect(fd, InetAddress.UNSPECIFIED, 0);
} catch (ErrnoException errnoException) {
throw errnoException.rethrowAsIOException();
}
- if (socket != null) {
- socket.disconnect();
- }
return this;
}
+ /**
+ * Initialize the state associated with being disconnected, optionally syncing the socket if
+ * there is one.
+ * @hide used to sync state, non-private to avoid synthetic method
+ */
+ void onDisconnect(boolean updateSocketState) {
+ connected = false;
+ connectAddress = null;
+ if (updateSocketState && socket != null && socket.isConnected()) {
+ socket.onDisconnect();
+ }
+ }
+
@Override
public SocketAddress receive(ByteBuffer target) throws IOException {
target.checkWritable();
@@ -191,7 +246,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
SocketAddress retAddr = null;
DatagramPacket receivePacket;
int oldposition = target.position();
- int received = 0;
+ int received;
// TODO: disallow mapped buffers and lose this conditional?
if (target.hasArray()) {
receivePacket = new DatagramPacket(target.array(), target.position() + target.arrayOffset(), target.remaining());
@@ -200,7 +255,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
}
do {
received = IoBridge.recvfrom(false, fd, receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength(), 0, receivePacket, isConnected());
- if (receivePacket != null && receivePacket.getAddress() != null) {
+ if (receivePacket.getAddress() != null) {
if (received > 0) {
if (target.hasArray()) {
target.position(oldposition + received);
@@ -220,10 +275,10 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
SocketAddress retAddr = null;
DatagramPacket receivePacket = new DatagramPacket(EmptyArray.BYTE, 0);
int oldposition = target.position();
- int received = 0;
+ int received;
do {
received = IoBridge.recvfrom(false, fd, target, 0, receivePacket, isConnected());
- if (receivePacket != null && receivePacket.getAddress() != null) {
+ if (receivePacket.getAddress() != null) {
// copy the data of received packet
if (received > 0) {
target.position(oldposition + received);
@@ -259,7 +314,9 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
if (sendCount > 0) {
source.position(oldPosition + sendCount);
}
- isBound = true;
+ if (!isBound) {
+ onBind(true /* updateSocketState */);
+ }
} finally {
end(sendCount >= 0);
}
@@ -276,7 +333,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
return 0;
}
- int readCount = 0;
+ int readCount;
if (target.isDirect() || target.hasArray()) {
readCount = readImpl(target);
if (readCount > 0) {
@@ -405,11 +462,12 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
}
@Override protected synchronized void implCloseSelectableChannel() throws IOException {
- connected = false;
+ // A closed channel is not connected.
+ onDisconnect(true /* updateSocketState */);
+ IoBridge.closeAndSignalBlockedThreads(fd);
+
if (socket != null && !socket.isClosed()) {
- socket.close();
- } else {
- IoBridge.closeSocket(fd);
+ socket.onClose();
}
}
@@ -420,7 +478,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
/*
* Status check, must be open.
*/
- private void checkOpen() throws IOException {
+ private void checkOpen() throws ClosedChannelException {
if (!isOpen()) {
throw new ClosedChannelException();
}
@@ -460,15 +518,29 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
/*
* The internal datagramChannelImpl.
*/
- private DatagramChannelImpl channelImpl;
+ private final DatagramChannelImpl channelImpl;
/*
* Constructor initialize the datagramSocketImpl and datagramChannelImpl
*/
- DatagramSocketAdapter(DatagramSocketImpl socketimpl,
- DatagramChannelImpl channelImpl) {
+ DatagramSocketAdapter(DatagramSocketImpl socketimpl, DatagramChannelImpl channelImpl) {
super(socketimpl);
this.channelImpl = channelImpl;
+
+ // Sync state socket state with the channel it is being created from
+ if (channelImpl.isBound) {
+ onBind(channelImpl.localAddress, channelImpl.localPort);
+ }
+ if (channelImpl.connected) {
+ onConnect(
+ channelImpl.connectAddress.getAddress(),
+ channelImpl.connectAddress.getPort());
+ } else {
+ onDisconnect();
+ }
+ if (!channelImpl.isOpen()) {
+ onClose();
+ }
}
/*
@@ -479,92 +551,78 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
return channelImpl;
}
- /**
- * @see java.net.DatagramSocket#isBound()
- */
- @Override
- public boolean isBound() {
- return channelImpl.isBound;
- }
-
- /**
- * @see java.net.DatagramSocket#isConnected()
- */
- @Override
- public boolean isConnected() {
- return channelImpl.isConnected();
- }
-
- /**
- * @see java.net.DatagramSocket#getInetAddress()
- */
@Override
- public InetAddress getInetAddress() {
- if (channelImpl.connectAddress == null) {
- return null;
- }
- return channelImpl.connectAddress.getAddress();
- }
-
- @Override public InetAddress getLocalAddress() {
- try {
- return IoBridge.getSocketLocalAddress(channelImpl.fd);
- } catch (SocketException ex) {
- return null;
+ public void bind(SocketAddress localAddr) throws SocketException {
+ if (channelImpl.isConnected()) {
+ throw new AlreadyConnectedException();
}
+ super.bind(localAddr);
+ channelImpl.onBind(false /* updateSocketState */);
}
- /**
- * @see java.net.DatagramSocket#getPort()
- */
@Override
- public int getPort() {
- if (channelImpl.connectAddress == null) {
- return -1;
+ public void connect(SocketAddress peer) throws SocketException {
+ if (isConnected()) {
+ // RI compatibility: If the socket is already connected this fails.
+ throw new IllegalStateException("Socket is already connected.");
}
- return channelImpl.connectAddress.getPort();
+ super.connect(peer);
+ // Connect may have performed an implicit bind(). Sync up here.
+ channelImpl.onBind(false /* updateSocketState */);
+
+ InetSocketAddress inetSocketAddress = (InetSocketAddress) peer;
+ channelImpl.onConnect(
+ inetSocketAddress.getAddress(), inetSocketAddress.getPort(),
+ false /* updateSocketState */);
}
- /**
- * @see java.net.DatagramSocket#bind(java.net.SocketAddress)
- */
@Override
- public void bind(SocketAddress localAddr) throws SocketException {
- if (channelImpl.isConnected()) {
- throw new AlreadyConnectedException();
+ public void connect(InetAddress address, int port) {
+ // To avoid implementing connect() twice call this.connect(SocketAddress) in preference
+ // to super.connect().
+ try {
+ connect(new InetSocketAddress(address, port));
+ } catch (SocketException e) {
+ // Ignored - there is nothing we can report here.
}
- super.bind(localAddr);
- channelImpl.isBound = true;
}
- /**
- * @see java.net.DatagramSocket#receive(java.net.DatagramPacket)
- */
@Override
public void receive(DatagramPacket packet) throws IOException {
if (!channelImpl.isBlocking()) {
throw new IllegalBlockingModeException();
}
+
+ boolean wasBound = isBound();
super.receive(packet);
+ if (!wasBound) {
+ // DatagramSocket.receive() will implicitly bind if it hasn't been done explicitly.
+ // Sync the channel state with the socket.
+ channelImpl.onBind(false /* updateSocketState */);
+ }
}
- /**
- * @see java.net.DatagramSocket#send(java.net.DatagramPacket)
- */
@Override
public void send(DatagramPacket packet) throws IOException {
if (!channelImpl.isBlocking()) {
throw new IllegalBlockingModeException();
}
+
+ // DatagramSocket.send() will implicitly bind if it hasn't been done explicitly. Force
+ // bind() here so that the channel state stays in sync with the socket.
+ boolean wasBound = isBound();
super.send(packet);
+ if (!wasBound) {
+ // DatagramSocket.send() will implicitly bind if it hasn't been done explicitly.
+ // Sync the channel state with the socket.
+ channelImpl.onBind(false /* updateSocketState */);
+ }
}
- /**
- * @see java.net.DatagramSocket#close()
- */
@Override
public void close() {
synchronized (channelImpl) {
+ super.close();
if (channelImpl.isOpen()) {
try {
channelImpl.close();
@@ -572,21 +630,13 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
// Ignore
}
}
- super.close();
}
}
- /**
- * @see java.net.DatagramSocket#disconnect()
- */
@Override
public void disconnect() {
- try {
- channelImpl.disconnect();
- } catch (IOException e) {
- // Ignore
- }
super.disconnect();
+ channelImpl.onDisconnect(false /* updateSocketState */);
}
}
}
diff --git a/luni/src/main/java/java/nio/DirectByteBuffer.java b/luni/src/main/java/java/nio/DirectByteBuffer.java
index 1d12f2e..f19fb42 100644
--- a/luni/src/main/java/java/nio/DirectByteBuffer.java
+++ b/luni/src/main/java/java/nio/DirectByteBuffer.java
@@ -30,15 +30,16 @@ class DirectByteBuffer extends MappedByteBuffer {
private final boolean isReadOnly;
protected DirectByteBuffer(MemoryBlock block, int capacity, int offset, boolean isReadOnly, MapMode mapMode) {
- super(block, capacity, mapMode);
+ super(block, capacity, mapMode, block.toLong() + offset);
long baseSize = block.getSize();
+ // We're throwing this exception after we passed a bogus value
+ // to the superclass constructor, but it doesn't make any
+ // difference in this case.
if (baseSize >= 0 && (capacity + offset) > baseSize) {
throw new IllegalArgumentException("capacity + offset > baseSize");
}
- this.effectiveDirectAddress = block.toLong() + offset;
-
this.offset = offset;
this.isReadOnly = isReadOnly;
}
@@ -49,6 +50,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
private static DirectByteBuffer copy(DirectByteBuffer other, int markOfOther, boolean isReadOnly) {
+ other.checkNotFreed();
DirectByteBuffer buf = new DirectByteBuffer(other.block, other.capacity(), other.offset, isReadOnly, other.mapMode);
buf.limit = other.limit;
buf.position = other.position();
@@ -61,6 +63,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer compact() {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -76,6 +79,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer slice() {
+ checkNotFreed();
return new DirectByteBuffer(block, remaining(), offset + position, isReadOnly, mapMode);
}
@@ -84,6 +88,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override byte[] protectedArray() {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -104,6 +109,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public final ByteBuffer get(byte[] dst, int dstOffset, int byteCount) {
+ checkIsAccessible();
checkGetBounds(1, dst.length, dstOffset, byteCount);
this.block.peekByteArray(offset + position, dst, dstOffset, byteCount);
position += byteCount;
@@ -111,42 +117,49 @@ class DirectByteBuffer extends MappedByteBuffer {
}
final void get(char[] dst, int dstOffset, int charCount) {
+ checkIsAccessible();
int byteCount = checkGetBounds(SizeOf.CHAR, dst.length, dstOffset, charCount);
this.block.peekCharArray(offset + position, dst, dstOffset, charCount, order.needsSwap);
position += byteCount;
}
final void get(double[] dst, int dstOffset, int doubleCount) {
+ checkIsAccessible();
int byteCount = checkGetBounds(SizeOf.DOUBLE, dst.length, dstOffset, doubleCount);
this.block.peekDoubleArray(offset + position, dst, dstOffset, doubleCount, order.needsSwap);
position += byteCount;
}
final void get(float[] dst, int dstOffset, int floatCount) {
+ checkIsAccessible();
int byteCount = checkGetBounds(SizeOf.FLOAT, dst.length, dstOffset, floatCount);
this.block.peekFloatArray(offset + position, dst, dstOffset, floatCount, order.needsSwap);
position += byteCount;
}
final void get(int[] dst, int dstOffset, int intCount) {
+ checkIsAccessible();
int byteCount = checkGetBounds(SizeOf.INT, dst.length, dstOffset, intCount);
this.block.peekIntArray(offset + position, dst, dstOffset, intCount, order.needsSwap);
position += byteCount;
}
final void get(long[] dst, int dstOffset, int longCount) {
+ checkIsAccessible();
int byteCount = checkGetBounds(SizeOf.LONG, dst.length, dstOffset, longCount);
this.block.peekLongArray(offset + position, dst, dstOffset, longCount, order.needsSwap);
position += byteCount;
}
final void get(short[] dst, int dstOffset, int shortCount) {
+ checkIsAccessible();
int byteCount = checkGetBounds(SizeOf.SHORT, dst.length, dstOffset, shortCount);
this.block.peekShortArray(offset + position, dst, dstOffset, shortCount, order.needsSwap);
position += byteCount;
}
@Override public final byte get() {
+ checkIsAccessible();
if (position == limit) {
throw new BufferUnderflowException();
}
@@ -154,11 +167,13 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public final byte get(int index) {
+ checkIsAccessible();
checkIndex(index);
return this.block.peekByte(offset + index);
}
@Override public final char getChar() {
+ checkIsAccessible();
int newPosition = position + SizeOf.CHAR;
if (newPosition > limit) {
throw new BufferUnderflowException();
@@ -169,11 +184,13 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public final char getChar(int index) {
+ checkIsAccessible();
checkIndex(index, SizeOf.CHAR);
return (char) this.block.peekShort(offset + index, order);
}
@Override public final double getDouble() {
+ checkIsAccessible();
int newPosition = position + SizeOf.DOUBLE;
if (newPosition > limit) {
throw new BufferUnderflowException();
@@ -184,11 +201,13 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public final double getDouble(int index) {
+ checkIsAccessible();
checkIndex(index, SizeOf.DOUBLE);
return Double.longBitsToDouble(this.block.peekLong(offset + index, order));
}
@Override public final float getFloat() {
+ checkIsAccessible();
int newPosition = position + SizeOf.FLOAT;
if (newPosition > limit) {
throw new BufferUnderflowException();
@@ -199,11 +218,13 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public final float getFloat(int index) {
+ checkIsAccessible();
checkIndex(index, SizeOf.FLOAT);
return Float.intBitsToFloat(this.block.peekInt(offset + index, order));
}
@Override public final int getInt() {
+ checkIsAccessible();
int newPosition = position + SizeOf.INT;
if (newPosition > limit) {
throw new BufferUnderflowException();
@@ -214,11 +235,13 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public final int getInt(int index) {
+ checkIsAccessible();
checkIndex(index, SizeOf.INT);
return this.block.peekInt(offset + index, order);
}
@Override public final long getLong() {
+ checkIsAccessible();
int newPosition = position + SizeOf.LONG;
if (newPosition > limit) {
throw new BufferUnderflowException();
@@ -229,11 +252,13 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public final long getLong(int index) {
+ checkIsAccessible();
checkIndex(index, SizeOf.LONG);
return this.block.peekLong(offset + index, order);
}
@Override public final short getShort() {
+ checkIsAccessible();
int newPosition = position + SizeOf.SHORT;
if (newPosition > limit) {
throw new BufferUnderflowException();
@@ -244,6 +269,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public final short getShort(int index) {
+ checkIsAccessible();
checkIndex(index, SizeOf.SHORT);
return this.block.peekShort(offset + index, order);
}
@@ -252,35 +278,56 @@ class DirectByteBuffer extends MappedByteBuffer {
return true;
}
+ /** @hide */
+ @Override public final boolean isAccessible() {
+ return block.isAccessible();
+ }
+
+ /** @hide */
+ @Override public void setAccessible(boolean accessible) {
+ block.setAccessible(accessible);
+ }
+
+ /**
+ * Invalidates the buffer. Subsequent operations which touch the inner
+ * buffer will throw {@link IllegalStateException}.
+ */
public final void free() {
block.free();
}
@Override public final CharBuffer asCharBuffer() {
+ checkNotFreed();
return ByteBufferAsCharBuffer.asCharBuffer(this);
}
@Override public final DoubleBuffer asDoubleBuffer() {
+ checkNotFreed();
return ByteBufferAsDoubleBuffer.asDoubleBuffer(this);
}
@Override public final FloatBuffer asFloatBuffer() {
+ checkNotFreed();
return ByteBufferAsFloatBuffer.asFloatBuffer(this);
}
@Override public final IntBuffer asIntBuffer() {
+ checkNotFreed();
return ByteBufferAsIntBuffer.asIntBuffer(this);
}
@Override public final LongBuffer asLongBuffer() {
+ checkNotFreed();
return ByteBufferAsLongBuffer.asLongBuffer(this);
}
@Override public final ShortBuffer asShortBuffer() {
+ checkNotFreed();
return ByteBufferAsShortBuffer.asShortBuffer(this);
}
@Override public ByteBuffer put(byte value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -292,6 +339,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer put(int index, byte value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -301,6 +349,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer put(byte[] src, int srcOffset, int byteCount) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -311,42 +360,49 @@ class DirectByteBuffer extends MappedByteBuffer {
}
final void put(char[] src, int srcOffset, int charCount) {
+ checkIsAccessible();
int byteCount = checkPutBounds(SizeOf.CHAR, src.length, srcOffset, charCount);
this.block.pokeCharArray(offset + position, src, srcOffset, charCount, order.needsSwap);
position += byteCount;
}
final void put(double[] src, int srcOffset, int doubleCount) {
+ checkIsAccessible();
int byteCount = checkPutBounds(SizeOf.DOUBLE, src.length, srcOffset, doubleCount);
this.block.pokeDoubleArray(offset + position, src, srcOffset, doubleCount, order.needsSwap);
position += byteCount;
}
final void put(float[] src, int srcOffset, int floatCount) {
+ checkIsAccessible();
int byteCount = checkPutBounds(SizeOf.FLOAT, src.length, srcOffset, floatCount);
this.block.pokeFloatArray(offset + position, src, srcOffset, floatCount, order.needsSwap);
position += byteCount;
}
final void put(int[] src, int srcOffset, int intCount) {
+ checkIsAccessible();
int byteCount = checkPutBounds(SizeOf.INT, src.length, srcOffset, intCount);
this.block.pokeIntArray(offset + position, src, srcOffset, intCount, order.needsSwap);
position += byteCount;
}
final void put(long[] src, int srcOffset, int longCount) {
+ checkIsAccessible();
int byteCount = checkPutBounds(SizeOf.LONG, src.length, srcOffset, longCount);
this.block.pokeLongArray(offset + position, src, srcOffset, longCount, order.needsSwap);
position += byteCount;
}
final void put(short[] src, int srcOffset, int shortCount) {
+ checkIsAccessible();
int byteCount = checkPutBounds(SizeOf.SHORT, src.length, srcOffset, shortCount);
this.block.pokeShortArray(offset + position, src, srcOffset, shortCount, order.needsSwap);
position += byteCount;
}
@Override public ByteBuffer putChar(char value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -360,6 +416,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putChar(int index, char value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -369,6 +426,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putDouble(double value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -382,6 +440,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putDouble(int index, double value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -391,6 +450,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putFloat(float value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -404,6 +464,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putFloat(int index, float value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -413,6 +474,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putInt(int value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -426,6 +488,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putInt(int index, int value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -435,6 +498,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putLong(long value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -448,6 +512,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putLong(int index, long value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -457,6 +522,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putShort(short value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -470,6 +536,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
@Override public ByteBuffer putShort(int index, short value) {
+ checkIsAccessible();
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
@@ -477,4 +544,18 @@ class DirectByteBuffer extends MappedByteBuffer {
this.block.pokeShort(offset + index, value, order);
return this;
}
+
+ private void checkIsAccessible() {
+ checkNotFreed();
+ if (!block.isAccessible()) {
+ throw new IllegalStateException("buffer is inaccessible");
+ }
+ }
+
+ private void checkNotFreed() {
+ if (block.isFreed()) {
+ throw new IllegalStateException("buffer was freed");
+ }
+ }
+
}
diff --git a/luni/src/main/java/java/nio/DoubleArrayBuffer.java b/luni/src/main/java/java/nio/DoubleArrayBuffer.java
index f8e4e59..a2d9e79 100644
--- a/luni/src/main/java/java/nio/DoubleArrayBuffer.java
+++ b/luni/src/main/java/java/nio/DoubleArrayBuffer.java
@@ -33,7 +33,7 @@ final class DoubleArrayBuffer extends DoubleBuffer {
}
private DoubleArrayBuffer(int capacity, double[] backingArray, int arrayOffset, boolean isReadOnly) {
- super(capacity);
+ super(capacity, 0);
this.backingArray = backingArray;
this.arrayOffset = arrayOffset;
this.isReadOnly = isReadOnly;
diff --git a/luni/src/main/java/java/nio/DoubleBuffer.java b/luni/src/main/java/java/nio/DoubleBuffer.java
index 2fa74d2..f3062fb 100644
--- a/luni/src/main/java/java/nio/DoubleBuffer.java
+++ b/luni/src/main/java/java/nio/DoubleBuffer.java
@@ -81,7 +81,7 @@ public abstract class DoubleBuffer extends Buffer implements
* the length, must not be negative and not greater than
* {@code array.length - start}.
* @return the created double buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code doubleCount} is invalid.
*/
public static DoubleBuffer wrap(double[] array, int start, int doubleCount) {
@@ -92,8 +92,8 @@ public abstract class DoubleBuffer extends Buffer implements
return buf;
}
- DoubleBuffer(int capacity) {
- super(3, capacity, null);
+ DoubleBuffer(int capacity, long effectiveDirectAddress) {
+ super(3, capacity, effectiveDirectAddress);
}
public final double[] array() {
@@ -127,7 +127,7 @@ public abstract class DoubleBuffer extends Buffer implements
* limit is set to capacity; the mark is cleared.
*
* @return this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract DoubleBuffer compact();
@@ -141,7 +141,7 @@ public abstract class DoubleBuffer extends Buffer implements
* @return a negative value if this is less than {@code other}; 0 if this
* equals to {@code other}; a positive value if this is greater
* than {@code other}.
- * @exception ClassCastException
+ * @throws ClassCastException
* if {@code other} is not a double buffer.
*/
public int compareTo(DoubleBuffer otherBuffer) {
@@ -223,7 +223,7 @@ public abstract class DoubleBuffer extends Buffer implements
* 1.
*
* @return the double at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is equal or greater than limit.
*/
public abstract double get();
@@ -238,7 +238,7 @@ public abstract class DoubleBuffer extends Buffer implements
* @param dst
* the destination double array.
* @return this buffer.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code dst.length} is greater than {@code remaining()}.
*/
public DoubleBuffer get(double[] dst) {
@@ -259,9 +259,9 @@ public abstract class DoubleBuffer extends Buffer implements
* the number of doubles to read, must be no less than zero and
* not greater than {@code dst.length - dstOffset}.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code dstOffset} or {@code doubleCount} is invalid.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code doubleCount} is greater than {@code remaining()}.
*/
public DoubleBuffer get(double[] dst, int dstOffset, int doubleCount) {
@@ -281,7 +281,7 @@ public abstract class DoubleBuffer extends Buffer implements
* @param index
* the index, must not be negative and less than limit.
* @return a double at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
*/
public abstract double get(int index);
@@ -360,9 +360,9 @@ public abstract class DoubleBuffer extends Buffer implements
* @param d
* the double to write.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is equal or greater than limit.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract DoubleBuffer put(double d);
@@ -377,9 +377,9 @@ public abstract class DoubleBuffer extends Buffer implements
* @param src
* the source double array.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code src.length}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public final DoubleBuffer put(double[] src) {
@@ -400,11 +400,11 @@ public abstract class DoubleBuffer extends Buffer implements
* the number of doubles to write, must be no less than zero and
* not greater than {@code src.length - srcOffset}.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code doubleCount}.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code srcOffset} or {@code doubleCount} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public DoubleBuffer put(double[] src, int srcOffset, int doubleCount) {
@@ -426,12 +426,12 @@ public abstract class DoubleBuffer extends Buffer implements
* @param src
* the source double buffer.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code src.remaining()} is greater than this buffer's
* {@code remaining()}.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* if {@code src} is this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public DoubleBuffer put(DoubleBuffer src) {
@@ -459,9 +459,9 @@ public abstract class DoubleBuffer extends Buffer implements
* @param d
* the double to write.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract DoubleBuffer put(int index, double d);
diff --git a/luni/src/main/java/java/nio/FileChannelImpl.java b/luni/src/main/java/java/nio/FileChannelImpl.java
index a1be7fb..d72b9f0 100644
--- a/luni/src/main/java/java/nio/FileChannelImpl.java
+++ b/luni/src/main/java/java/nio/FileChannelImpl.java
@@ -17,12 +17,17 @@
package java.nio;
+import android.system.ErrnoException;
+import android.system.StructFlock;
+import android.util.MutableLong;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
+import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
+import java.nio.channels.FileLockInterruptionException;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.OverlappingFileLockException;
@@ -32,11 +37,8 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
-import libcore.io.ErrnoException;
import libcore.io.Libcore;
-import libcore.io.StructFlock;
-import libcore.util.MutableLong;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* Our concrete implementation of the abstract FileChannel class.
@@ -50,7 +52,7 @@ final class FileChannelImpl extends FileChannel {
}
};
- private final Object stream;
+ private final Closeable ioObject;
private final FileDescriptor fd;
private final int mode;
@@ -61,9 +63,9 @@ final class FileChannelImpl extends FileChannel {
* Create a new file channel implementation class that wraps the given
* fd and operates in the specified mode.
*/
- public FileChannelImpl(Object stream, FileDescriptor fd, int mode) {
+ public FileChannelImpl(Closeable ioObject, FileDescriptor fd, int mode) {
this.fd = fd;
- this.stream = stream;
+ this.ioObject = ioObject;
this.mode = mode;
}
@@ -86,9 +88,7 @@ final class FileChannelImpl extends FileChannel {
}
protected void implCloseChannel() throws IOException {
- if (stream instanceof Closeable) {
- ((Closeable) stream).close();
- }
+ ioObject.close();
}
private FileLock basicLock(long position, long size, boolean shared, boolean wait) throws IOException {
@@ -166,7 +166,11 @@ final class FileChannelImpl extends FileChannel {
resultLock = basicLock(position, size, shared, true);
completed = true;
} finally {
- end(completed);
+ try {
+ end(completed);
+ } catch (ClosedByInterruptException e) {
+ throw new FileLockInterruptionException();
+ }
}
}
return resultLock;
@@ -461,6 +465,9 @@ final class FileChannelImpl extends FileChannel {
throw errnoException.rethrowAsIOException();
}
}
+ if (position() > size) {
+ position(size);
+ }
return this;
}
diff --git a/luni/src/main/java/java/nio/FloatArrayBuffer.java b/luni/src/main/java/java/nio/FloatArrayBuffer.java
index 698174c..ac512a5 100644
--- a/luni/src/main/java/java/nio/FloatArrayBuffer.java
+++ b/luni/src/main/java/java/nio/FloatArrayBuffer.java
@@ -33,7 +33,7 @@ final class FloatArrayBuffer extends FloatBuffer {
}
private FloatArrayBuffer(int capacity, float[] backingArray, int arrayOffset, boolean isReadOnly) {
- super(capacity);
+ super(capacity, 0);
this.backingArray = backingArray;
this.arrayOffset = arrayOffset;
this.isReadOnly = isReadOnly;
diff --git a/luni/src/main/java/java/nio/FloatBuffer.java b/luni/src/main/java/java/nio/FloatBuffer.java
index cb7e55e..68514c5 100644
--- a/luni/src/main/java/java/nio/FloatBuffer.java
+++ b/luni/src/main/java/java/nio/FloatBuffer.java
@@ -80,9 +80,9 @@ public abstract class FloatBuffer extends Buffer implements
* the length, must not be negative and not greater than
* {@code array.length - start}.
* @return the created float buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code floatCount} is invalid.
- * @exception NullPointerException
+ * @throws NullPointerException
* if {@code array} is null.
*/
public static FloatBuffer wrap(float[] array, int start, int floatCount) {
@@ -93,8 +93,8 @@ public abstract class FloatBuffer extends Buffer implements
return buf;
}
- FloatBuffer(int capacity) {
- super(2, capacity, null);
+ FloatBuffer(int capacity, long effectiveDirectAddress) {
+ super(2, capacity, effectiveDirectAddress);
}
public final float[] array() {
@@ -128,7 +128,7 @@ public abstract class FloatBuffer extends Buffer implements
* limit is set to capacity; the mark is cleared.
*
* @return this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract FloatBuffer compact();
@@ -142,7 +142,7 @@ public abstract class FloatBuffer extends Buffer implements
* @return a negative value if this is less than {@code otherBuffer}; 0 if
* this equals to {@code otherBuffer}; a positive value if this is
* greater than {@code otherBuffer}.
- * @exception ClassCastException
+ * @throws ClassCastException
* if {@code otherBuffer} is not a float buffer.
*/
public int compareTo(FloatBuffer otherBuffer) {
@@ -224,7 +224,7 @@ public abstract class FloatBuffer extends Buffer implements
* 1.
*
* @return the float at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is equal or greater than limit.
*/
public abstract float get();
@@ -239,7 +239,7 @@ public abstract class FloatBuffer extends Buffer implements
* @param dst
* the destination float array.
* @return this buffer.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code dst.length} is greater than {@code remaining()}.
*/
public FloatBuffer get(float[] dst) {
@@ -260,9 +260,9 @@ public abstract class FloatBuffer extends Buffer implements
* the number of floats to read, must be no less than zero and no
* greater than {@code dst.length - dstOffset}.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code dstOffset} or {@code floatCount} is invalid.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code floatCount} is greater than {@code remaining()}.
*/
public FloatBuffer get(float[] dst, int dstOffset, int floatCount) {
@@ -282,7 +282,7 @@ public abstract class FloatBuffer extends Buffer implements
* @param index
* the index, must not be negative and less than limit.
* @return a float at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
*/
public abstract float get(int index);
@@ -359,9 +359,9 @@ public abstract class FloatBuffer extends Buffer implements
* @param f
* the float to write.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is equal or greater than limit.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract FloatBuffer put(float f);
@@ -376,9 +376,9 @@ public abstract class FloatBuffer extends Buffer implements
* @param src
* the source float array.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code src.length}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public final FloatBuffer put(float[] src) {
@@ -399,11 +399,11 @@ public abstract class FloatBuffer extends Buffer implements
* the number of floats to write, must be no less than zero and
* no greater than {@code src.length - srcOffset}.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code floatCount}.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code srcOffset} or {@code floatCount} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public FloatBuffer put(float[] src, int srcOffset, int floatCount) {
@@ -425,12 +425,12 @@ public abstract class FloatBuffer extends Buffer implements
* @param src
* the source float buffer.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code src.remaining()} is greater than this buffer's
* {@code remaining()}.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* if {@code src} is this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public FloatBuffer put(FloatBuffer src) {
@@ -458,9 +458,9 @@ public abstract class FloatBuffer extends Buffer implements
* @param f
* the float to write.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract FloatBuffer put(int index, float f);
diff --git a/luni/src/main/java/java/nio/IntArrayBuffer.java b/luni/src/main/java/java/nio/IntArrayBuffer.java
index a5f9f39..423b1af 100644
--- a/luni/src/main/java/java/nio/IntArrayBuffer.java
+++ b/luni/src/main/java/java/nio/IntArrayBuffer.java
@@ -33,7 +33,7 @@ final class IntArrayBuffer extends IntBuffer {
}
private IntArrayBuffer(int capacity, int[] backingArray, int arrayOffset, boolean isReadOnly) {
- super(capacity);
+ super(capacity, 0);
this.backingArray = backingArray;
this.arrayOffset = arrayOffset;
this.isReadOnly = isReadOnly;
diff --git a/luni/src/main/java/java/nio/IntBuffer.java b/luni/src/main/java/java/nio/IntBuffer.java
index a20f6c2..50e95b0 100644
--- a/luni/src/main/java/java/nio/IntBuffer.java
+++ b/luni/src/main/java/java/nio/IntBuffer.java
@@ -78,7 +78,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* the length, must not be negative and not greater than
* {@code array.length - start}.
* @return the created int buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code intCount} is invalid.
*/
public static IntBuffer wrap(int[] array, int start, int intCount) {
@@ -89,8 +89,8 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
return buf;
}
- IntBuffer(int capacity) {
- super(2, capacity, null);
+ IntBuffer(int capacity, long effectiveDirectAddress) {
+ super(2, capacity, effectiveDirectAddress);
}
public final int[] array() {
@@ -124,7 +124,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* limit is set to capacity; the mark is cleared.
*
* @return this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract IntBuffer compact();
@@ -138,7 +138,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* @return a negative value if this is less than {@code other}; 0 if this
* equals to {@code other}; a positive value if this is greater
* than {@code other}.
- * @exception ClassCastException
+ * @throws ClassCastException
* if {@code other} is not an int buffer.
*/
public int compareTo(IntBuffer otherBuffer) {
@@ -210,7 +210,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* Returns the int at the current position and increases the position by 1.
*
* @return the int at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is equal or greater than limit.
*/
public abstract int get();
@@ -225,7 +225,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* @param dst
* the destination int array.
* @return this buffer.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code dst.length} is greater than {@code remaining()}.
*/
public IntBuffer get(int[] dst) {
@@ -246,9 +246,9 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* the number of ints to read, must be no less than zero and not
* greater than {@code dst.length - dstOffset}.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code dstOffset} or {@code intCount} is invalid.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code intCount} is greater than {@code remaining()}.
*/
public IntBuffer get(int[] dst, int dstOffset, int intCount) {
@@ -268,7 +268,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* @param index
* the index, must not be negative and less than limit.
* @return an int at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
*/
public abstract int get(int index);
@@ -345,9 +345,9 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* @param i
* the int to write.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is equal or greater than limit.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract IntBuffer put(int i);
@@ -362,9 +362,9 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* @param src
* the source int array.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code src.length}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public final IntBuffer put(int[] src) {
@@ -385,11 +385,11 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* the number of ints to write, must be no less than zero and not
* greater than {@code src.length - srcOffset}.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code intCount}.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code srcOffset} or {@code intCount} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public IntBuffer put(int[] src, int srcOffset, int intCount) {
@@ -414,12 +414,12 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* @param src
* the source int buffer.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code src.remaining()} is greater than this buffer's
* {@code remaining()}.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* if {@code src} is this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public IntBuffer put(IntBuffer src) {
@@ -447,9 +447,9 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
* @param i
* the int to write.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract IntBuffer put(int index, int i);
diff --git a/luni/src/main/java/java/nio/IoVec.java b/luni/src/main/java/java/nio/IoVec.java
index f14f4f2..e20709c 100644
--- a/luni/src/main/java/java/nio/IoVec.java
+++ b/luni/src/main/java/java/nio/IoVec.java
@@ -16,10 +16,10 @@
package java.nio;
+import android.system.ErrnoException;
import java.io.FileDescriptor;
import java.io.IOException;
import libcore.io.Libcore;
-import libcore.io.ErrnoException;
/**
* Used to implement java.nio read(ByteBuffer[])/write(ByteBuffer[]) operations as POSIX readv(2)
diff --git a/luni/src/main/java/java/nio/LongArrayBuffer.java b/luni/src/main/java/java/nio/LongArrayBuffer.java
index 6419d73..f03a91e 100644
--- a/luni/src/main/java/java/nio/LongArrayBuffer.java
+++ b/luni/src/main/java/java/nio/LongArrayBuffer.java
@@ -33,7 +33,7 @@ final class LongArrayBuffer extends LongBuffer {
}
private LongArrayBuffer(int capacity, long[] backingArray, int arrayOffset, boolean isReadOnly) {
- super(capacity);
+ super(capacity, 0);
this.backingArray = backingArray;
this.arrayOffset = arrayOffset;
this.isReadOnly = isReadOnly;
diff --git a/luni/src/main/java/java/nio/LongBuffer.java b/luni/src/main/java/java/nio/LongBuffer.java
index 55adab6..09f8418 100644
--- a/luni/src/main/java/java/nio/LongBuffer.java
+++ b/luni/src/main/java/java/nio/LongBuffer.java
@@ -80,7 +80,7 @@ public abstract class LongBuffer extends Buffer implements
* the length, must not be negative and not greater than
* {@code array.length - start}.
* @return the created long buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code longCount} is invalid.
*/
public static LongBuffer wrap(long[] array, int start, int longCount) {
@@ -91,8 +91,8 @@ public abstract class LongBuffer extends Buffer implements
return buf;
}
- LongBuffer(int capacity) {
- super(3, capacity, null);
+ LongBuffer(int capacity, long effectiveDirectAddress) {
+ super(3, capacity, effectiveDirectAddress);
}
public final long[] array() {
@@ -126,7 +126,7 @@ public abstract class LongBuffer extends Buffer implements
* limit is set to capacity; the mark is cleared.
*
* @return this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract LongBuffer compact();
@@ -140,7 +140,7 @@ public abstract class LongBuffer extends Buffer implements
* @return a negative value if this is less than {@code otherBuffer}; 0 if
* this equals to {@code otherBuffer}; a positive value if this is
* greater than {@code otherBuffer}
- * @exception ClassCastException
+ * @throws ClassCastException
* if {@code otherBuffer} is not a long buffer.
*/
public int compareTo(LongBuffer otherBuffer) {
@@ -212,7 +212,7 @@ public abstract class LongBuffer extends Buffer implements
* Returns the long at the current position and increase the position by 1.
*
* @return the long at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is equal or greater than limit.
*/
public abstract long get();
@@ -227,7 +227,7 @@ public abstract class LongBuffer extends Buffer implements
* @param dst
* the destination long array.
* @return this buffer.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code dst.length} is greater than {@code remaining()}.
*/
public LongBuffer get(long[] dst) {
@@ -248,9 +248,9 @@ public abstract class LongBuffer extends Buffer implements
* the number of longs to read, must be no less than zero and not
* greater than {@code dst.length - dstOffset}.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code dstOffset} or {@code longCount} is invalid.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code longCount} is greater than {@code remaining()}.
*/
public LongBuffer get(long[] dst, int dstOffset, int longCount) {
@@ -270,7 +270,7 @@ public abstract class LongBuffer extends Buffer implements
* @param index
* the index, must not be negative and less than limit.
* @return the long at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
*/
public abstract long get(int index);
@@ -349,9 +349,9 @@ public abstract class LongBuffer extends Buffer implements
* @param l
* the long to write.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is equal or greater than limit.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract LongBuffer put(long l);
@@ -366,9 +366,9 @@ public abstract class LongBuffer extends Buffer implements
* @param src
* the source long array.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code src.length}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public final LongBuffer put(long[] src) {
@@ -389,11 +389,11 @@ public abstract class LongBuffer extends Buffer implements
* the number of longs to write, must be no less than zero and
* not greater than {@code src.length - srcOffset}.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code longCount}.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code srcOffset} or {@code longCount} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public LongBuffer put(long[] src, int srcOffset, int longCount) {
@@ -415,12 +415,12 @@ public abstract class LongBuffer extends Buffer implements
* @param src
* the source long buffer.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code src.remaining()} is greater than this buffer's
* {@code remaining()}.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* if {@code src} is this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public LongBuffer put(LongBuffer src) {
@@ -448,9 +448,9 @@ public abstract class LongBuffer extends Buffer implements
* @param l
* the long to write.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract LongBuffer put(int index, long l);
diff --git a/luni/src/main/java/java/nio/MappedByteBuffer.java b/luni/src/main/java/java/nio/MappedByteBuffer.java
index 2d44d0f..ce19c0c 100644
--- a/luni/src/main/java/java/nio/MappedByteBuffer.java
+++ b/luni/src/main/java/java/nio/MappedByteBuffer.java
@@ -16,11 +16,11 @@
package java.nio;
+import android.system.ErrnoException;
import java.nio.channels.FileChannel.MapMode;
-import libcore.io.ErrnoException;
import libcore.io.Libcore;
-import libcore.io.Memory;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.MS_SYNC;
+import static android.system.OsConstants._SC_PAGE_SIZE;
/**
* {@code MappedByteBuffer} is a special kind of direct byte buffer which maps a
@@ -38,10 +38,12 @@ import static libcore.io.OsConstants.*;
*/
public abstract class MappedByteBuffer extends ByteBuffer {
final MapMode mapMode;
+ final MemoryBlock block;
- MappedByteBuffer(MemoryBlock block, int capacity, MapMode mapMode) {
- super(capacity, block);
+ MappedByteBuffer(MemoryBlock block, int capacity, MapMode mapMode, long effectiveDirectAddress) {
+ super(capacity, effectiveDirectAddress);
this.mapMode = mapMode;
+ this.block = block;
}
/**
diff --git a/luni/src/main/java/java/nio/MemoryBlock.java b/luni/src/main/java/java/nio/MemoryBlock.java
index 718b020..b62e3c6 100644
--- a/luni/src/main/java/java/nio/MemoryBlock.java
+++ b/luni/src/main/java/java/nio/MemoryBlock.java
@@ -17,14 +17,18 @@
package java.nio;
+import android.system.ErrnoException;
import dalvik.system.VMRuntime;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.channels.FileChannel.MapMode;
-import libcore.io.ErrnoException;
import libcore.io.Libcore;
import libcore.io.Memory;
-import static libcore.io.OsConstants.*;
+
+import static android.system.OsConstants.MAP_PRIVATE;
+import static android.system.OsConstants.MAP_SHARED;
+import static android.system.OsConstants.PROT_READ;
+import static android.system.OsConstants.PROT_WRITE;
class MemoryBlock {
/**
@@ -44,8 +48,8 @@ class MemoryBlock {
// a state where munmap(2) could return an error.
throw new AssertionError(errnoException);
}
- address = 0;
}
+ super.free();
}
@Override protected void finalize() throws Throwable {
@@ -74,7 +78,7 @@ class MemoryBlock {
@Override public void free() {
array = null;
- address = 0;
+ super.free();
}
}
@@ -90,6 +94,8 @@ class MemoryBlock {
protected long address;
protected final long size;
+ private boolean accessible;
+ private boolean freed;
public static MemoryBlock mmap(FileDescriptor fd, long offset, long size, MapMode mapMode) throws IOException {
if (size == 0) {
@@ -134,6 +140,8 @@ class MemoryBlock {
private MemoryBlock(long address, long size) {
this.address = address;
this.size = size;
+ accessible = true;
+ freed = false;
}
// Used to support array/arrayOffset/hasArray for direct buffers.
@@ -142,6 +150,20 @@ class MemoryBlock {
}
public void free() {
+ address = 0;
+ freed = true;
+ }
+
+ public boolean isFreed() {
+ return freed;
+ }
+
+ public boolean isAccessible() {
+ return !isFreed() && accessible;
+ }
+
+ public final void setAccessible(boolean accessible) {
+ this.accessible = accessible;
}
public final void pokeByte(int offset, byte value) {
diff --git a/luni/src/main/java/java/nio/NIOAccess.java b/luni/src/main/java/java/nio/NIOAccess.java
index 12af44d..ddb102e 100644
--- a/luni/src/main/java/java/nio/NIOAccess.java
+++ b/luni/src/main/java/java/nio/NIOAccess.java
@@ -24,17 +24,11 @@ final class NIOAccess {
/**
* Returns the underlying native pointer to the data of the given
* Buffer starting at the Buffer's current position, or 0 if the
- * Buffer is not backed by native heap storage. Note that this is
- * different than what the Harmony implementation calls a "base
- * address."
- *
- * @param b the Buffer to be queried
- * @return the native pointer to the Buffer's data at its current
- * position, or 0 if there is none
+ * Buffer is not backed by native heap storage.
*/
static long getBasePointer(Buffer b) {
long address = b.effectiveDirectAddress;
- if (address == 0) {
+ if (address == 0L) {
return 0L;
}
return address + (b.position << b._elementSizeShift);
@@ -43,10 +37,6 @@ final class NIOAccess {
/**
* Returns the underlying Java array containing the data of the
* given Buffer, or null if the Buffer is not backed by a Java array.
- *
- * @param b the Buffer to be queried
- * @return the Java array containing the Buffer's data, or null if
- * there is none
*/
static Object getBaseArray(Buffer b) {
return b.hasArray() ? b.array() : null;
@@ -58,9 +48,6 @@ final class NIOAccess {
* the actual start of the data. The start of the data takes into
* account the Buffer's current position. This method is only
* meaningful if getBaseArray() returns non-null.
- *
- * @param b the Buffer to be queried
- * @return the data offset in bytes to the start of this Buffer's data
*/
static int getBaseArrayOffset(Buffer b) {
return b.hasArray() ? ((b.arrayOffset() + b.position) << b._elementSizeShift) : 0;
diff --git a/luni/src/main/java/java/nio/NioUtils.java b/luni/src/main/java/java/nio/NioUtils.java
index a1a46b6..f2a0b10 100644
--- a/luni/src/main/java/java/nio/NioUtils.java
+++ b/luni/src/main/java/java/nio/NioUtils.java
@@ -16,8 +16,12 @@
package java.nio;
+import java.io.Closeable;
import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
+import java.util.Set;
/**
* @hide internal use only
@@ -43,8 +47,8 @@ public final class NioUtils {
/**
* Helps bridge between io and nio.
*/
- public static FileChannel newFileChannel(Object stream, FileDescriptor fd, int mode) {
- return new FileChannelImpl(stream, fd, mode);
+ public static FileChannel newFileChannel(Closeable ioObject, FileDescriptor fd, int mode) {
+ return new FileChannelImpl(ioObject, fd, mode);
}
/**
diff --git a/luni/src/main/java/java/nio/PipeImpl.java b/luni/src/main/java/java/nio/PipeImpl.java
index d4dde3b..ebc8a91 100644
--- a/luni/src/main/java/java/nio/PipeImpl.java
+++ b/luni/src/main/java/java/nio/PipeImpl.java
@@ -16,16 +16,16 @@
package java.nio;
+import android.system.ErrnoException;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.channels.Pipe;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
import libcore.io.Libcore;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/*
* Implements {@link java.nio.channels.Pipe}.
diff --git a/luni/src/main/java/java/nio/SelectorImpl.java b/luni/src/main/java/java/nio/SelectorImpl.java
index d63fa63..45406b1 100644
--- a/luni/src/main/java/java/nio/SelectorImpl.java
+++ b/luni/src/main/java/java/nio/SelectorImpl.java
@@ -15,33 +15,38 @@
*/
package java.nio;
+import android.system.ErrnoException;
+import android.system.StructPollfd;
import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
import java.io.IOException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.IllegalSelectorException;
-import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
-import static java.nio.channels.SelectionKey.*;
import java.nio.channels.Selector;
-import java.nio.channels.SocketChannel;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.AbstractSelectionKey;
import java.nio.channels.spi.AbstractSelector;
import java.nio.channels.spi.SelectorProvider;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.UnsafeArrayList;
-import libcore.io.ErrnoException;
import libcore.io.IoBridge;
import libcore.io.IoUtils;
import libcore.io.Libcore;
-import libcore.io.StructPollfd;
-import libcore.util.EmptyArray;
-import static libcore.io.OsConstants.*;
+
+import static android.system.OsConstants.EINTR;
+import static android.system.OsConstants.POLLERR;
+import static android.system.OsConstants.POLLHUP;
+import static android.system.OsConstants.POLLIN;
+import static android.system.OsConstants.POLLOUT;
+import static java.nio.channels.SelectionKey.OP_ACCEPT;
+import static java.nio.channels.SelectionKey.OP_CONNECT;
+import static java.nio.channels.SelectionKey.OP_READ;
+import static java.nio.channels.SelectionKey.OP_WRITE;
/*
* Default implementation of java.nio.channels.Selector
@@ -255,7 +260,7 @@ final class SelectorImpl extends AbstractSelector {
int ops = key.interestOpsNoCheck();
int selectedOps = 0;
- if ((pollFd.revents & POLLHUP) != 0) {
+ if ((pollFd.revents & POLLHUP) != 0 || (pollFd.revents & POLLERR) != 0) {
// If there was an error condition, we definitely want to wake listeners,
// regardless of what they're waiting for. Failure is always interesting.
selectedOps |= ops;
@@ -321,6 +326,7 @@ final class SelectorImpl extends AbstractSelector {
try {
Libcore.os.write(wakeupOut, new byte[] { 1 }, 0, 1);
} catch (ErrnoException ignored) {
+ } catch (InterruptedIOException ignored) {
}
return this;
}
diff --git a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java
index 3fb61a3..ae33672 100644
--- a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java
+++ b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java
@@ -17,13 +17,13 @@
package java.nio;
+import android.system.ErrnoException;
import java.io.FileDescriptor;
import java.io.IOException;
-import java.net.PlainServerSocketImpl;
+import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
-import java.net.SocketImpl;
import java.net.SocketTimeoutException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.IllegalBlockingModeException;
@@ -31,9 +31,11 @@ import java.nio.channels.NotYetBoundException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
-import libcore.io.ErrnoException;
+import java.nio.channels.UnresolvedAddressException;
+import java.nio.channels.UnsupportedAddressTypeException;
+import java.util.Set;
import libcore.io.IoUtils;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* The default ServerSocketChannel.
@@ -41,31 +43,28 @@ import static libcore.io.OsConstants.*;
final class ServerSocketChannelImpl extends ServerSocketChannel implements FileDescriptorChannel {
private final ServerSocketAdapter socket;
- private final SocketImpl impl;
-
- private boolean isBound = false;
private final Object acceptLock = new Object();
public ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
super(sp);
this.socket = new ServerSocketAdapter(this);
- this.impl = socket.getImpl$();
}
@Override public ServerSocket socket() {
return socket;
}
- @Override public SocketChannel accept() throws IOException {
+ @Override
+ public SocketChannel accept() throws IOException {
if (!isOpen()) {
throw new ClosedChannelException();
}
- if (!isBound) {
+ if (!socket.isBound()) {
throw new NotYetBoundException();
}
- // Create an empty socket channel. This will be populated by ServerSocketAdapter.accept.
+ // Create an empty socket channel. This will be populated by ServerSocketAdapter.implAccept.
SocketChannelImpl result = new SocketChannelImpl(provider(), false);
try {
begin();
@@ -81,9 +80,9 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD
}
}
} finally {
- end(result.socket().isConnected());
+ end(result.isConnected());
}
- return result.socket().isConnected() ? result : null;
+ return result.isConnected() ? result : null;
}
private boolean shouldThrowSocketTimeoutExceptionFromAccept(SocketTimeoutException e) {
@@ -100,17 +99,19 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD
}
@Override protected void implConfigureBlocking(boolean blocking) throws IOException {
- IoUtils.setBlocking(impl.getFD$(), blocking);
+ IoUtils.setBlocking(socket.getFD$(), blocking);
}
+ @Override
synchronized protected void implCloseSelectableChannel() throws IOException {
if (!socket.isClosed()) {
socket.close();
}
}
+ @Override
public FileDescriptor getFD() {
- return impl.getFD$();
+ return socket.getFD$();
}
private static class ServerSocketAdapter extends ServerSocket {
@@ -120,13 +121,8 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD
this.channelImpl = aChannelImpl;
}
- @Override public void bind(SocketAddress localAddress, int backlog) throws IOException {
- super.bind(localAddress, backlog);
- channelImpl.isBound = true;
- }
-
@Override public Socket accept() throws IOException {
- if (!channelImpl.isBound) {
+ if (!isBound()) {
throw new IllegalBlockingModeException();
}
SocketChannel sc = channelImpl.accept();
@@ -142,9 +138,12 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD
try {
synchronized (this) {
super.implAccept(clientSocket);
- clientSocketChannel.setConnected();
- clientSocketChannel.setBound(true);
- clientSocketChannel.finishAccept();
+
+ // Sync the client socket's associated channel state with the Socket and OS.
+ InetSocketAddress remoteAddress =
+ new InetSocketAddress(
+ clientSocket.getInetAddress(), clientSocket.getPort());
+ clientSocketChannel.onAccept(remoteAddress, false /* updateSocketState */);
}
connectOK = true;
} finally {
@@ -159,23 +158,17 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD
return channelImpl;
}
- @Override public boolean isBound() {
- return channelImpl.isBound;
- }
-
- @Override public void bind(SocketAddress localAddress) throws IOException {
- super.bind(localAddress);
- channelImpl.isBound = true;
- }
-
@Override public void close() throws IOException {
synchronized (channelImpl) {
+ super.close();
if (channelImpl.isOpen()) {
channelImpl.close();
- } else {
- super.close();
}
}
}
+
+ private FileDescriptor getFD$() {
+ return super.getImpl$().getFD$();
+ }
}
}
diff --git a/luni/src/main/java/java/nio/ShortArrayBuffer.java b/luni/src/main/java/java/nio/ShortArrayBuffer.java
index a092cb0..3c41174 100644
--- a/luni/src/main/java/java/nio/ShortArrayBuffer.java
+++ b/luni/src/main/java/java/nio/ShortArrayBuffer.java
@@ -33,7 +33,7 @@ final class ShortArrayBuffer extends ShortBuffer {
}
private ShortArrayBuffer(int capacity, short[] backingArray, int arrayOffset, boolean isReadOnly) {
- super(capacity);
+ super(capacity, 0);
this.backingArray = backingArray;
this.arrayOffset = arrayOffset;
this.isReadOnly = isReadOnly;
diff --git a/luni/src/main/java/java/nio/ShortBuffer.java b/luni/src/main/java/java/nio/ShortBuffer.java
index 8da01ed..cbc5a47 100644
--- a/luni/src/main/java/java/nio/ShortBuffer.java
+++ b/luni/src/main/java/java/nio/ShortBuffer.java
@@ -80,7 +80,7 @@ public abstract class ShortBuffer extends Buffer implements
* the length, must not be negative and not greater than
* {@code array.length - start}.
* @return the created short buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code start} or {@code shortCount} is invalid.
*/
public static ShortBuffer wrap(short[] array, int start, int shortCount) {
@@ -91,8 +91,8 @@ public abstract class ShortBuffer extends Buffer implements
return buf;
}
- ShortBuffer(int capacity) {
- super(1, capacity, null);
+ ShortBuffer(int capacity, long effectiveDirectAddress) {
+ super(1, capacity, effectiveDirectAddress);
}
public final short[] array() {
@@ -126,7 +126,7 @@ public abstract class ShortBuffer extends Buffer implements
* limit is set to capacity; the mark is cleared.
*
* @return this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ShortBuffer compact();
@@ -140,7 +140,7 @@ public abstract class ShortBuffer extends Buffer implements
* @return a negative value if this is less than {@code otherBuffer}; 0 if
* this equals to {@code otherBuffer}; a positive value if this is
* greater than {@code otherBuffer}.
- * @exception ClassCastException
+ * @throws ClassCastException
* if {@code otherBuffer} is not a short buffer.
*/
public int compareTo(ShortBuffer otherBuffer) {
@@ -213,7 +213,7 @@ public abstract class ShortBuffer extends Buffer implements
* 1.
*
* @return the short at the current position.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if the position is equal or greater than limit.
*/
public abstract short get();
@@ -228,7 +228,7 @@ public abstract class ShortBuffer extends Buffer implements
* @param dst
* the destination short array.
* @return this buffer.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code dst.length} is greater than {@code remaining()}.
*/
public ShortBuffer get(short[] dst) {
@@ -249,9 +249,9 @@ public abstract class ShortBuffer extends Buffer implements
* the number of shorts to read, must be no less than zero and
* not greater than {@code dst.length - dstOffset}.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code dstOffset} or {@code shortCount} is invalid.
- * @exception BufferUnderflowException
+ * @throws BufferUnderflowException
* if {@code shortCount} is greater than {@code remaining()}.
*/
public ShortBuffer get(short[] dst, int dstOffset, int shortCount) {
@@ -271,7 +271,7 @@ public abstract class ShortBuffer extends Buffer implements
* @param index
* the index, must not be negative and less than limit.
* @return a short at the specified index.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
*/
public abstract short get(int index);
@@ -348,9 +348,9 @@ public abstract class ShortBuffer extends Buffer implements
* @param s
* the short to write.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if position is equal or greater than limit.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ShortBuffer put(short s);
@@ -365,9 +365,9 @@ public abstract class ShortBuffer extends Buffer implements
* @param src
* the source short array.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code src.length}.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public final ShortBuffer put(short[] src) {
@@ -388,11 +388,11 @@ public abstract class ShortBuffer extends Buffer implements
* the number of shorts to write, must be no less than zero and
* not greater than {@code src.length - srcOffset}.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code remaining()} is less than {@code shortCount}.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if either {@code srcOffset} or {@code shortCount} is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public ShortBuffer put(short[] src, int srcOffset, int shortCount) {
@@ -414,12 +414,12 @@ public abstract class ShortBuffer extends Buffer implements
* @param src
* the source short buffer.
* @return this buffer.
- * @exception BufferOverflowException
+ * @throws BufferOverflowException
* if {@code src.remaining()} is greater than this buffer's
* {@code remaining()}.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* if {@code src} is this buffer.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public ShortBuffer put(ShortBuffer src) {
@@ -447,9 +447,9 @@ public abstract class ShortBuffer extends Buffer implements
* @param s
* the short to write.
* @return this buffer.
- * @exception IndexOutOfBoundsException
+ * @throws IndexOutOfBoundsException
* if index is invalid.
- * @exception ReadOnlyBufferException
+ * @throws ReadOnlyBufferException
* if no changes may be made to the contents of this buffer.
*/
public abstract ShortBuffer put(int index, short s);
diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java
index 714005f..d5cb716 100644
--- a/luni/src/main/java/java/nio/SocketChannelImpl.java
+++ b/luni/src/main/java/java/nio/SocketChannelImpl.java
@@ -17,9 +17,12 @@
package java.nio;
+import android.system.ErrnoException;
import java.io.FileDescriptor;
-import java.io.IOException;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
import java.io.InputStream;
+import java.io.IOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.Inet4Address;
@@ -30,7 +33,6 @@ import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketUtils;
-import java.net.UnknownHostException;
import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ConnectionPendingException;
@@ -38,15 +40,15 @@ import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.NoConnectionPendingException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SocketChannel;
+import java.nio.channels.spi.SelectorProvider;
import java.nio.channels.UnresolvedAddressException;
import java.nio.channels.UnsupportedAddressTypeException;
-import java.nio.channels.spi.SelectorProvider;
import java.util.Arrays;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
+import java.util.Set;
import libcore.io.IoBridge;
import libcore.io.IoUtils;
-import static libcore.io.OsConstants.*;
+import libcore.io.Libcore;
+import static android.system.OsConstants.*;
/*
* The default implementation class of java.nio.channels.SocketChannel.
@@ -74,6 +76,7 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
// The address to be connected.
private InetSocketAddress connectAddress = null;
+ // The local address the socket is bound to.
private InetAddress localAddress = null;
private int localPort;
@@ -133,20 +136,34 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
return socket;
}
- @Override
- synchronized public boolean isConnected() {
- return status == SOCKET_STATUS_CONNECTED;
- }
-
- /*
- * Status setting used by other class.
+ /**
+ * Initialise the isBound, localAddress and localPort state from the file descriptor. Used when
+ * some or all of the bound state has been left to the OS to decide, or when the Socket handled
+ * bind() or connect().
+ *
+ * @param updateSocketState
+ * if the associated socket (if present) needs to be updated
+ * @hide package visible for other nio classes
*/
- synchronized void setConnected() {
- status = SOCKET_STATUS_CONNECTED;
+ void onBind(boolean updateSocketState) {
+ SocketAddress sa;
+ try {
+ sa = Libcore.os.getsockname(fd);
+ } catch (ErrnoException errnoException) {
+ throw new AssertionError(errnoException);
+ }
+ isBound = true;
+ InetSocketAddress localSocketAddress = (InetSocketAddress) sa;
+ localAddress = localSocketAddress.getAddress();
+ localPort = localSocketAddress.getPort();
+ if (updateSocketState && socket != null) {
+ socket.onBind(localAddress, localPort);
+ }
}
- void setBound(boolean flag) {
- isBound = flag;
+ @Override
+ synchronized public boolean isConnected() {
+ return status == SOCKET_STATUS_CONNECTED;
}
@Override
@@ -169,16 +186,22 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
normalAddr = InetAddress.getLocalHost();
}
+ boolean isBlocking = isBlocking();
boolean finished = false;
+ int newStatus;
try {
- if (isBlocking()) {
+ if (isBlocking) {
begin();
}
- finished = IoBridge.connect(fd, normalAddr, port);
- isBound = finished;
+ // When in blocking mode, IoBridge.connect() will return without an exception when the
+ // socket is connected. When in non-blocking mode it will return without an exception
+ // without knowing the result of the connection attempt, which could still be going on.
+ IoBridge.connect(fd, normalAddr, port);
+ newStatus = isBlocking ? SOCKET_STATUS_CONNECTED : SOCKET_STATUS_PENDING;
+ finished = true;
} catch (IOException e) {
if (isEINPROGRESS(e)) {
- status = SOCKET_STATUS_PENDING;
+ newStatus = SOCKET_STATUS_PENDING;
} else {
if (isOpen()) {
close();
@@ -187,26 +210,36 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
throw e;
}
} finally {
- if (isBlocking()) {
+ if (isBlocking) {
end(finished);
}
}
- initLocalAddressAndPort();
- connectAddress = inetSocketAddress;
- if (socket != null) {
- socket.socketImpl().initRemoteAddressAndPort(connectAddress.getAddress(),
- connectAddress.getPort());
+ // If the channel was not bound, a connection attempt will have caused an implicit bind() to
+ // take place. Keep the local address state held by the channel and the socket up to date.
+ if (!isBound) {
+ onBind(true /* updateSocketState */);
}
- synchronized (this) {
- if (isBlocking()) {
- status = (finished ? SOCKET_STATUS_CONNECTED : SOCKET_STATUS_UNCONNECTED);
- } else {
- status = SOCKET_STATUS_PENDING;
- }
+ // Keep the connected state held by the channel and the socket up to date.
+ onConnectStatusChanged(inetSocketAddress, newStatus, true /* updateSocketState */);
+
+ return status == SOCKET_STATUS_CONNECTED;
+ }
+
+ /**
+ * Initialise the connect() state with the supplied information.
+ *
+ * @param updateSocketState
+ * if the associated socket (if present) needs to be updated
+ * @hide package visible for other nio classes
+ */
+ void onConnectStatusChanged(InetSocketAddress address, int status, boolean updateSocketState) {
+ this.status = status;
+ connectAddress = address;
+ if (status == SOCKET_STATUS_CONNECTED && updateSocketState && socket != null) {
+ socket.onConnect(connectAddress.getAddress(), connectAddress.getPort());
}
- return finished;
}
private boolean isEINPROGRESS(IOException e) {
@@ -222,21 +255,6 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
return false;
}
- private void initLocalAddressAndPort() {
- SocketAddress sa;
- try {
- sa = Libcore.os.getsockname(fd);
- } catch (ErrnoException errnoException) {
- throw new AssertionError(errnoException);
- }
- InetSocketAddress isa = (InetSocketAddress) sa;
- localAddress = isa.getAddress();
- localPort = isa.getPort();
- if (socket != null) {
- socket.socketImpl().initLocalPort(localPort);
- }
- }
-
@Override
public boolean finishConnect() throws IOException {
synchronized (this) {
@@ -257,7 +275,6 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
InetAddress inetAddress = connectAddress.getAddress();
int port = connectAddress.getPort();
finished = IoBridge.isConnected(fd, inetAddress, port, 0, 0); // Return immediately.
- isBound = finished;
} catch (ConnectException e) {
if (isOpen()) {
close();
@@ -270,15 +287,13 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
synchronized (this) {
status = (finished ? SOCKET_STATUS_CONNECTED : status);
- isBound = finished;
+ if (finished && socket != null) {
+ socket.onConnect(connectAddress.getAddress(), connectAddress.getPort());
+ }
}
return finished;
}
- void finishAccept() {
- initLocalAddressAndPort();
- }
-
@Override
public int read(ByteBuffer dst) throws IOException {
dst.checkWritable();
@@ -447,23 +462,17 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
}
/*
- * Get local address.
- */
- public InetAddress getLocalAddress() throws UnknownHostException {
- return isBound ? localAddress : Inet4Address.ANY;
- }
-
- /*
* Do really closing action here.
*/
@Override
protected synchronized void implCloseSelectableChannel() throws IOException {
if (status != SOCKET_STATUS_CLOSED) {
status = SOCKET_STATUS_CLOSED;
+ // IoBridge.closeAndSignalBlockedThreads(fd) is idempotent: It is safe to call on an
+ // already-closed file descriptor.
+ IoBridge.closeAndSignalBlockedThreads(fd);
if (socket != null && !socket.isClosed()) {
- socket.close();
- } else {
- IoBridge.closeSocket(fd);
+ socket.onClose();
}
}
}
@@ -479,6 +488,12 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
return fd;
}
+ /* @hide used by ServerSocketChannelImpl to sync channel state during accept() */
+ public void onAccept(InetSocketAddress remoteAddress, boolean updateSocketState) {
+ onBind(updateSocketState);
+ onConnectStatusChanged(remoteAddress, SOCKET_STATUS_CONNECTED, updateSocketState);
+ }
+
/*
* Adapter classes for internal socket.
*/
@@ -486,15 +501,24 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
private final SocketChannelImpl channel;
private final PlainSocketImpl socketImpl;
- SocketAdapter(PlainSocketImpl socketImpl, SocketChannelImpl channel) throws SocketException {
+ SocketAdapter(PlainSocketImpl socketImpl, SocketChannelImpl channel)
+ throws SocketException {
super(socketImpl);
this.socketImpl = socketImpl;
this.channel = channel;
SocketUtils.setCreated(this);
- }
- PlainSocketImpl socketImpl() {
- return socketImpl;
+ // Sync state socket state with the channel it is being created from
+ if (channel.isBound) {
+ onBind(channel.localAddress, channel.localPort);
+ }
+ if (channel.isConnected()) {
+ onConnect(channel.connectAddress.getAddress(), channel.connectAddress.getPort());
+ }
+ if (!channel.isOpen()) {
+ onClose();
+ }
+
}
@Override
@@ -503,25 +527,6 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
}
@Override
- public boolean isBound() {
- return channel.isBound;
- }
-
- @Override
- public boolean isConnected() {
- return channel.isConnected();
- }
-
- @Override
- public InetAddress getLocalAddress() {
- try {
- return channel.getLocalAddress();
- } catch (UnknownHostException e) {
- return null;
- }
- }
-
- @Override
public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
if (!channel.isBlocking()) {
throw new IllegalBlockingModeException();
@@ -530,10 +535,11 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
throw new AlreadyConnectedException();
}
super.connect(remoteAddr, timeout);
- channel.initLocalAddressAndPort();
+ channel.onBind(false);
if (super.isConnected()) {
- channel.setConnected();
- channel.isBound = super.isBound();
+ InetSocketAddress remoteInetAddress = (InetSocketAddress) remoteAddr;
+ channel.onConnectStatusChanged(
+ remoteInetAddress, SOCKET_STATUS_CONNECTED, false /* updateSocketState */);
}
}
@@ -546,47 +552,29 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
throw new ConnectionPendingException();
}
super.bind(localAddr);
- channel.initLocalAddressAndPort();
- channel.isBound = true;
+ channel.onBind(false);
}
@Override
public void close() throws IOException {
synchronized (channel) {
+ super.close();
if (channel.isOpen()) {
+ // channel.close() recognizes the socket is closed and avoids recursion. There
+ // is no channel.onClose() because the "closed" field is private.
channel.close();
- } else {
- super.close();
}
- channel.status = SocketChannelImpl.SOCKET_STATUS_CLOSED;
}
}
@Override
public OutputStream getOutputStream() throws IOException {
- checkOpenAndConnected();
- if (isOutputShutdown()) {
- throw new SocketException("Socket output is shutdown");
- }
- return new SocketChannelOutputStream(channel);
+ return new BlockingCheckOutputStream(super.getOutputStream(), channel);
}
@Override
public InputStream getInputStream() throws IOException {
- checkOpenAndConnected();
- if (isInputShutdown()) {
- throw new SocketException("Socket input is shutdown");
- }
- return new SocketChannelInputStream(channel);
- }
-
- private void checkOpenAndConnected() throws SocketException {
- if (!channel.isOpen()) {
- throw new SocketException("Socket is closed");
- }
- if (!channel.isConnected()) {
- throw new SocketException("Socket is not connected");
- }
+ return new BlockingCheckInputStream(super.getInputStream(), channel);
}
@Override
@@ -596,86 +584,92 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
}
/*
- * This output stream delegates all operations to the associated channel.
* Throws an IllegalBlockingModeException if the channel is in non-blocking
* mode when performing write operations.
*/
- private static class SocketChannelOutputStream extends OutputStream {
+ private static class BlockingCheckOutputStream extends FilterOutputStream {
private final SocketChannel channel;
- public SocketChannelOutputStream(SocketChannel channel) {
+ public BlockingCheckOutputStream(OutputStream out, SocketChannel channel) {
+ super(out);
this.channel = channel;
}
- /*
- * Closes this stream and channel.
- *
- * @exception IOException thrown if an error occurs during the close
- */
@Override
- public void close() throws IOException {
- channel.close();
+ public void write(byte[] buffer, int offset, int byteCount) throws IOException {
+ checkBlocking();
+ out.write(buffer, offset, byteCount);
}
@Override
- public void write(byte[] buffer, int offset, int byteCount) throws IOException {
- Arrays.checkOffsetAndCount(buffer.length, offset, byteCount);
- ByteBuffer buf = ByteBuffer.wrap(buffer, offset, byteCount);
- if (!channel.isBlocking()) {
- throw new IllegalBlockingModeException();
- }
- channel.write(buf);
+ public void write(int oneByte) throws IOException {
+ checkBlocking();
+ out.write(oneByte);
}
@Override
- public void write(int oneByte) throws IOException {
+ public void write(byte[] buffer) throws IOException {
+ checkBlocking();
+ out.write(buffer);
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ // channel.close() recognizes the socket is closed and avoids recursion. There is no
+ // channel.onClose() because the "closed" field is private.
+ channel.close();
+ }
+
+ private void checkBlocking() {
if (!channel.isBlocking()) {
throw new IllegalBlockingModeException();
}
- ByteBuffer buffer = ByteBuffer.allocate(1);
- buffer.put(0, (byte) (oneByte & 0xFF));
- channel.write(buffer);
}
}
/*
- * This input stream delegates all operations to the associated channel.
* Throws an IllegalBlockingModeException if the channel is in non-blocking
* mode when performing read operations.
*/
- private static class SocketChannelInputStream extends InputStream {
+ private static class BlockingCheckInputStream extends FilterInputStream {
private final SocketChannel channel;
- public SocketChannelInputStream(SocketChannel channel) {
+ public BlockingCheckInputStream(InputStream in, SocketChannel channel) {
+ super(in);
this.channel = channel;
}
- /*
- * Closes this stream and channel.
- */
@Override
- public void close() throws IOException {
- channel.close();
+ public int read() throws IOException {
+ checkBlocking();
+ return in.read();
}
@Override
- public int read() throws IOException {
- if (!channel.isBlocking()) {
- throw new IllegalBlockingModeException();
- }
- ByteBuffer buf = ByteBuffer.allocate(1);
- int result = channel.read(buf);
- return (result == -1) ? result : (buf.get(0) & 0xff);
+ public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ checkBlocking();
+ return in.read(buffer, byteOffset, byteCount);
}
@Override
- public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
- Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount);
+ public int read(byte[] buffer) throws IOException {
+ checkBlocking();
+ return in.read(buffer);
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ // channel.close() recognizes the socket is closed and avoids recursion. There is no
+ // channel.onClose() because the "closed" field is private.
+ channel.close();
+ }
+
+ private void checkBlocking() {
if (!channel.isBlocking()) {
throw new IllegalBlockingModeException();
}
- ByteBuffer buf = ByteBuffer.wrap(buffer, byteOffset, byteCount);
- return channel.read(buf);
}
}
}
diff --git a/luni/src/main/java/java/nio/channels/DatagramChannel.java b/luni/src/main/java/java/nio/channels/DatagramChannel.java
index 486b168..2cff7f0 100644
--- a/luni/src/main/java/java/nio/channels/DatagramChannel.java
+++ b/luni/src/main/java/java/nio/channels/DatagramChannel.java
@@ -19,10 +19,13 @@ package java.nio.channels;
import java.io.IOException;
import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.SelectorProvider;
+import java.util.Set;
/**
* A {@code DatagramChannel} is a selectable channel that represents a partial
diff --git a/luni/src/main/java/java/nio/channels/FileChannel.java b/luni/src/main/java/java/nio/channels/FileChannel.java
index 298ea8b..d6c140b 100644
--- a/luni/src/main/java/java/nio/channels/FileChannel.java
+++ b/luni/src/main/java/java/nio/channels/FileChannel.java
@@ -77,7 +77,7 @@ import java.nio.channels.spi.AbstractInterruptibleChannel;
* content, size, etc.
*/
public abstract class FileChannel extends AbstractInterruptibleChannel
- implements GatheringByteChannel, ScatteringByteChannel, ByteChannel {
+ implements ByteChannel, GatheringByteChannel, ScatteringByteChannel {
/**
* {@code MapMode} defines file mapping mode constants.
@@ -281,10 +281,9 @@ public abstract class FileChannel extends AbstractInterruptibleChannel
long position, long size) throws IOException;
/**
- * Returns the current value of the file position pointer.
+ * Returns the current position as a positive integer number of bytes from
+ * the start of the file.
*
- * @return the current position as a positive integer number of bytes from
- * the start of the file.
* @throws ClosedChannelException
* if this channel is closed.
* @throws IOException
@@ -302,9 +301,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel
* succeed but they will fill the bytes between the current end of file and
* the new position with the required number of (unspecified) byte values.
*
- * @param offset
- * the new file position, in bytes.
- * @return the receiver.
+ * @return this.
* @throws IllegalArgumentException
* if the new position is negative.
* @throws ClosedChannelException
@@ -312,7 +309,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel
* @throws IOException
* if another I/O error occurs.
*/
- public abstract FileChannel position(long offset) throws IOException;
+ public abstract FileChannel position(long newPosition) throws IOException;
/**
* Reads bytes from this file channel into the given buffer.
@@ -362,7 +359,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel
* the buffer to receive the bytes.
* @param position
* the (non-negative) position at which to read the bytes.
- * @return the number of bytes actually read.
+ * @return the number of bytes actually read, or -1 if the end of the file has been reached.
* @throws AsynchronousCloseException
* if this channel is closed by another thread while this method
* is executing.
@@ -398,7 +395,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel
*
* @param buffers
* the array of byte buffers into which the bytes will be copied.
- * @return the number of bytes actually read.
+ * @return the number of bytes actually read, or -1 if the end of the file has been reached.
* @throws AsynchronousCloseException
* if this channel is closed by another thread during this read
* operation.
@@ -413,6 +410,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel
* if the channel has not been opened in a mode that permits
* reading.
*/
+ @Override
public final long read(ByteBuffer[] buffers) throws IOException {
return read(buffers, 0, buffers.length);
}
@@ -433,7 +431,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel
* the index of the first buffer to store bytes in.
* @param number
* the maximum number of buffers to store bytes in.
- * @return the number of bytes actually read.
+ * @return the number of bytes actually read, or -1 if the end of the file has been reached.
* @throws AsynchronousCloseException
* if this channel is closed by another thread during this read
* operation.
@@ -458,7 +456,6 @@ public abstract class FileChannel extends AbstractInterruptibleChannel
/**
* Returns the size of the file underlying this channel in bytes.
*
- * @return the size of the file in bytes.
* @throws ClosedChannelException
* if this channel is closed.
* @throws IOException
@@ -712,6 +709,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel
* @throws NonWritableChannelException
* if this channel was not opened for writing.
*/
+ @Override
public final long write(ByteBuffer[] buffers) throws IOException {
return write(buffers, 0, buffers.length);
}
@@ -752,6 +750,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel
* @throws NonWritableChannelException
* if this channel was not opened for writing.
*/
+ @Override
public abstract long write(ByteBuffer[] buffers, int offset, int length)
throws IOException;
}
diff --git a/luni/src/main/java/java/nio/channels/FileLock.java b/luni/src/main/java/java/nio/channels/FileLock.java
index 360f826..5b26475 100644
--- a/luni/src/main/java/java/nio/channels/FileLock.java
+++ b/luni/src/main/java/java/nio/channels/FileLock.java
@@ -108,8 +108,6 @@ public abstract class FileLock implements AutoCloseable {
/**
* Returns the lock's {@link FileChannel}.
- *
- * @return the channel.
*/
public final FileChannel channel() {
return channel;
diff --git a/luni/src/main/java/java/nio/channels/ServerSocketChannel.java b/luni/src/main/java/java/nio/channels/ServerSocketChannel.java
index f3c3390..ef50155 100644
--- a/luni/src/main/java/java/nio/channels/ServerSocketChannel.java
+++ b/luni/src/main/java/java/nio/channels/ServerSocketChannel.java
@@ -19,8 +19,10 @@ package java.nio.channels;
import java.io.IOException;
import java.net.ServerSocket;
+import java.net.SocketAddress;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.SelectorProvider;
+import java.util.Set;
/**
* A {@code ServerSocketChannel} is a partial abstraction of a selectable,
@@ -35,7 +37,6 @@ import java.nio.channels.spi.SelectorProvider;
* related {@code ServerSocket} instance.
*/
public abstract class ServerSocketChannel extends AbstractSelectableChannel {
-
/**
* Constructs a new {@link ServerSocketChannel}.
*
diff --git a/luni/src/main/java/java/nio/channels/SocketChannel.java b/luni/src/main/java/java/nio/channels/SocketChannel.java
index 753c88f..a91fccd 100644
--- a/luni/src/main/java/java/nio/channels/SocketChannel.java
+++ b/luni/src/main/java/java/nio/channels/SocketChannel.java
@@ -23,36 +23,44 @@ import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.SelectorProvider;
+import java.util.Set;
/**
* A {@code SocketChannel} is a selectable channel that provides a partial
- * abstraction of stream connecting socket. {@code socket()} returns the related
- * {@link Socket} instance which can handle the socket.
- * <p>
- * A socket channel is open but not connected when created by {@code open()}.
- * After connecting it by calling {@code connect(SocketAddress)}, it will remain
- * connected until it gets closed. If the connection is non-blocking then
- * {@code connect(SocketAddress)} is used to initiate the connection, followed
- * by a call of {@code finishConnect()} to perform the final steps of
- * connecting. {@code isConnectionPending()} indicates if the connection is
- * blocked or not; {@code isConnected()} indicates if the socket is finally
- * connected or not.
- * <p>
- * The input and output sides of a channel can be shut down independently and
- * asynchronously without closing the channel. The {@code shutdownInput} method
+ * abstraction of stream connecting socket.
+ *
+ * The {@link #socket()} method returns a {@link Socket} instance which
+ * allows a wider range of socket operations than {@code SocketChannel} itself.
+ *
+ * <p>A socket channel is open but not connected when created by {@link #open}.
+ * After connecting it by calling {@link #connect(SocketAddress)}, it will remain
+ * connected until closed.
+ *
+ * <p>If the connection is non-blocking then
+ * {@link #connect(SocketAddress)} is used to initiate the connection, followed
+ * by a call of {@link #finishConnect} to perform the final steps of
+ * connecting. {@link #isConnectionPending} to tests whether we're still
+ * trying to connect; {@link #isConnected} tests whether the socket connect
+ * completed successfully. Note that realistic code should use a {@link Selector}
+ * instead of polling. Note also that {@link java.net.Socket} can connect with a
+ * timeout, which is the most common use for a non-blocking connect.
+ *
+ * <p>The input and output sides of a channel can be shut down independently and
+ * asynchronously without closing the channel. The {@link Socket#shutdownInput} method
+ * on the socket returned by {@link #socket}
* is used for the input side of a channel and subsequent read operations return
* -1, which means end of stream. If another thread is blocked in a read
* operation when the shutdown occurs, the read will end without effect and
- * return end of stream. The {@code shutdownOutput} method is used for the
+ * return end of stream. Likewise the {@link Socket#shutdownOutput} method is used for the
* output side of the channel; subsequent write operations throw a
* {@link ClosedChannelException}. If the output is shut down and another thread
* is blocked in a write operation, an {@link AsynchronousCloseException} will
* be thrown to the pending thread.
- * <p>
- * Socket channels are thread-safe, no more than one thread can read or write at
- * any given time. The {@code connect(SocketAddress)} and {@code
- * finishConnect()} methods are synchronized against each other; when they are
- * processing, calls to {@code read} and {@code write} will block.
+ *
+ * <p>Socket channels are thread-safe, no more than one thread can read or write at
+ * any given time. The {@link #connect(SocketAddress)} and {@link
+ * #finishConnect()} methods are synchronized against each other; when they are
+ * processing, calls to {@link #read} and {@link #write} will block.
*/
public abstract class SocketChannel extends AbstractSelectableChannel implements
ByteChannel, ScatteringByteChannel, GatheringByteChannel {
diff --git a/luni/src/main/java/java/nio/charset/CharsetDecoderICU.java b/luni/src/main/java/java/nio/charset/CharsetDecoderICU.java
index 8b32efa..5dfdd9f 100644
--- a/luni/src/main/java/java/nio/charset/CharsetDecoderICU.java
+++ b/luni/src/main/java/java/nio/charset/CharsetDecoderICU.java
@@ -25,13 +25,13 @@ final class CharsetDecoderICU extends CharsetDecoder {
private static final int INPUT_OFFSET = 0;
private static final int OUTPUT_OFFSET = 1;
- private static final int INVALID_BYTES = 2;
+ private static final int INVALID_BYTE_COUNT = 2;
/*
* data[INPUT_OFFSET] = on input contains the start of input and on output the number of input bytes consumed
* data[OUTPUT_OFFSET] = on input contains the start of output and on output the number of output chars written
- * data[INVALID_BYTES] = number of invalid bytes
+ * data[INVALID_BYTE_COUNT] = number of invalid bytes
*/
- private int[] data = new int[3];
+ private final int[] data = new int[3];
/* handle to the ICU converter that is opened */
private long converterHandle = 0;
@@ -90,7 +90,7 @@ final class CharsetDecoderICU extends CharsetDecoder {
NativeConverter.resetByteToChar(converterHandle);
data[INPUT_OFFSET] = 0;
data[OUTPUT_OFFSET] = 0;
- data[INVALID_BYTES] = 0;
+ data[INVALID_BYTE_COUNT] = 0;
output = null;
input = null;
allocatedInput = null;
@@ -107,15 +107,15 @@ final class CharsetDecoderICU extends CharsetDecoder {
data[INPUT_OFFSET] = 0;
data[OUTPUT_OFFSET] = getArray(out);
- data[INVALID_BYTES] = 0; // Make sure we don't see earlier errors.
+ data[INVALID_BYTE_COUNT] = 0; // Make sure we don't see earlier errors.
int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, true);
if (ICU.U_FAILURE(error)) {
if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
return CoderResult.OVERFLOW;
} else if (error == ICU.U_TRUNCATED_CHAR_FOUND) {
- if (data[INPUT_OFFSET] > 0) {
- return CoderResult.malformedForLength(data[INPUT_OFFSET]);
+ if (data[INVALID_BYTE_COUNT] > 0) {
+ return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
}
}
}
@@ -140,9 +140,9 @@ final class CharsetDecoderICU extends CharsetDecoder {
if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
return CoderResult.OVERFLOW;
} else if (error == ICU.U_INVALID_CHAR_FOUND) {
- return CoderResult.unmappableForLength(data[INVALID_BYTES]);
+ return CoderResult.unmappableForLength(data[INVALID_BYTE_COUNT]);
} else if (error == ICU.U_ILLEGAL_CHAR_FOUND) {
- return CoderResult.malformedForLength(data[INVALID_BYTES]);
+ return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
} else {
throw new AssertionError(error);
}
diff --git a/luni/src/main/java/java/nio/charset/CharsetEncoderICU.java b/luni/src/main/java/java/nio/charset/CharsetEncoderICU.java
index 4bc4354..3583e19 100644
--- a/luni/src/main/java/java/nio/charset/CharsetEncoderICU.java
+++ b/luni/src/main/java/java/nio/charset/CharsetEncoderICU.java
@@ -40,7 +40,7 @@ final class CharsetEncoderICU extends CharsetEncoder {
private static final int INPUT_OFFSET = 0;
private static final int OUTPUT_OFFSET = 1;
- private static final int INVALID_CHARS = 2;
+ private static final int INVALID_CHAR_COUNT = 2;
/*
* data[INPUT_OFFSET] = on input contains the start of input and on output the number of input chars consumed
* data[OUTPUT_OFFSET] = on input contains the start of output and on output the number of output bytes written
@@ -118,7 +118,7 @@ final class CharsetEncoderICU extends CharsetEncoder {
NativeConverter.resetCharToByte(converterHandle);
data[INPUT_OFFSET] = 0;
data[OUTPUT_OFFSET] = 0;
- data[INVALID_CHARS] = 0;
+ data[INVALID_CHAR_COUNT] = 0;
output = null;
input = null;
allocatedInput = null;
@@ -135,15 +135,15 @@ final class CharsetEncoderICU extends CharsetEncoder {
data[INPUT_OFFSET] = 0;
data[OUTPUT_OFFSET] = getArray(out);
- data[INVALID_CHARS] = 0; // Make sure we don't see earlier errors.
+ data[INVALID_CHAR_COUNT] = 0; // Make sure we don't see earlier errors.
int error = NativeConverter.encode(converterHandle, input, inEnd, output, outEnd, data, true);
if (ICU.U_FAILURE(error)) {
if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
return CoderResult.OVERFLOW;
} else if (error == ICU.U_TRUNCATED_CHAR_FOUND) {
- if (data[INPUT_OFFSET] > 0) {
- return CoderResult.malformedForLength(data[INPUT_OFFSET]);
+ if (data[INVALID_CHAR_COUNT] > 0) {
+ return CoderResult.malformedForLength(data[INVALID_CHAR_COUNT]);
}
}
}
@@ -161,7 +161,7 @@ final class CharsetEncoderICU extends CharsetEncoder {
data[INPUT_OFFSET] = getArray(in);
data[OUTPUT_OFFSET]= getArray(out);
- data[INVALID_CHARS] = 0; // Make sure we don't see earlier errors.
+ data[INVALID_CHAR_COUNT] = 0; // Make sure we don't see earlier errors.
try {
int error = NativeConverter.encode(converterHandle, input, inEnd, output, outEnd, data, false);
@@ -169,9 +169,9 @@ final class CharsetEncoderICU extends CharsetEncoder {
if (error == ICU.U_BUFFER_OVERFLOW_ERROR) {
return CoderResult.OVERFLOW;
} else if (error == ICU.U_INVALID_CHAR_FOUND) {
- return CoderResult.unmappableForLength(data[INVALID_CHARS]);
+ return CoderResult.unmappableForLength(data[INVALID_CHAR_COUNT]);
} else if (error == ICU.U_ILLEGAL_CHAR_FOUND) {
- return CoderResult.malformedForLength(data[INVALID_CHARS]);
+ return CoderResult.malformedForLength(data[INVALID_CHAR_COUNT]);
} else {
throw new AssertionError(error);
}
@@ -231,7 +231,7 @@ final class CharsetEncoderICU extends CharsetEncoder {
private void setPosition(ByteBuffer out) {
if (out.hasArray()) {
- out.position(out.position() + data[OUTPUT_OFFSET] - out.arrayOffset());
+ out.position(data[OUTPUT_OFFSET] - out.arrayOffset());
} else {
out.put(output, 0, data[OUTPUT_OFFSET]);
}
@@ -240,7 +240,17 @@ final class CharsetEncoderICU extends CharsetEncoder {
}
private void setPosition(CharBuffer in) {
- in.position(in.position() + data[INPUT_OFFSET] - data[INVALID_CHARS]);
+ int position = in.position() + data[INPUT_OFFSET] - data[INVALID_CHAR_COUNT];
+ if (position < 0) {
+ // The calculated position might be negative if we encountered an
+ // invalid char that spanned input buffers. We adjust it to 0 in this case.
+ //
+ // NOTE: The API doesn't allow us to adjust the position of the previous
+ // input buffer. (Doing that wouldn't serve any useful purpose anyway.)
+ position = 0;
+ }
+
+ in.position(position);
// release reference to input array, which may not be ours
input = null;
}
diff --git a/luni/src/main/java/java/security/AlgorithmParameterGenerator.java b/luni/src/main/java/java/security/AlgorithmParameterGenerator.java
index 61548d7..167204c 100644
--- a/luni/src/main/java/java/security/AlgorithmParameterGenerator.java
+++ b/luni/src/main/java/java/security/AlgorithmParameterGenerator.java
@@ -129,7 +129,8 @@ public class AlgorithmParameterGenerator {
/**
* Returns a new instance of {@code AlgorithmParameterGenerator} from the
- * specified provider for the specified algorithm.
+ * specified provider for the specified algorithm. The {@code provider}
+ * supplied does not have to be registered.
*
* @param algorithm
* the name of the algorithm to use.
diff --git a/luni/src/main/java/java/security/AlgorithmParameters.java b/luni/src/main/java/java/security/AlgorithmParameters.java
index 073460e..27af018 100644
--- a/luni/src/main/java/java/security/AlgorithmParameters.java
+++ b/luni/src/main/java/java/security/AlgorithmParameters.java
@@ -131,7 +131,8 @@ public class AlgorithmParameters {
/**
* Returns a new instance of {@code AlgorithmParameters} from the specified
- * provider for the specified algorithm.
+ * provider for the specified algorithm. The {@code provider} supplied does
+ * not have to be registered.
*
* @param algorithm
* the name of the algorithm to use.
diff --git a/luni/src/main/java/java/security/GuardedObject.java b/luni/src/main/java/java/security/GuardedObject.java
index 34a5113..dd2e98f 100644
--- a/luni/src/main/java/java/security/GuardedObject.java
+++ b/luni/src/main/java/java/security/GuardedObject.java
@@ -53,7 +53,7 @@ public class GuardedObject implements Serializable {
* SecurityException} is thrown.
*
* @return the guarded object.
- * @exception SecurityException
+ * @throws SecurityException
* if access is not granted to the guarded object.
*/
public Object getObject() throws SecurityException {
diff --git a/luni/src/main/java/java/security/KeyFactory.java b/luni/src/main/java/java/security/KeyFactory.java
index 3bd05b9..c22212f 100644
--- a/luni/src/main/java/java/security/KeyFactory.java
+++ b/luni/src/main/java/java/security/KeyFactory.java
@@ -112,7 +112,8 @@ public class KeyFactory {
/**
* Returns a new instance of {@code KeyFactory} that utilizes the specified
- * algorithm from the specified provider.
+ * algorithm from the specified provider. The {@code provider} supplied
+ * does not have to be registered.
*
* @param algorithm
* the name of the algorithm.
diff --git a/luni/src/main/java/java/security/KeyPairGenerator.java b/luni/src/main/java/java/security/KeyPairGenerator.java
index 8a713d0..d05e902 100644
--- a/luni/src/main/java/java/security/KeyPairGenerator.java
+++ b/luni/src/main/java/java/security/KeyPairGenerator.java
@@ -124,7 +124,8 @@ public abstract class KeyPairGenerator extends KeyPairGeneratorSpi {
/**
* Returns a new instance of {@code KeyPairGenerator} that utilizes the
- * specified algorithm from the specified provider.
+ * specified algorithm from the specified provider. The {@code provider}
+ * supplied does not have to be registered.
*
* @param algorithm
* the name of the algorithm to use
diff --git a/luni/src/main/java/java/security/KeyStore.java b/luni/src/main/java/java/security/KeyStore.java
index b0e4945..0611e59 100644
--- a/luni/src/main/java/java/security/KeyStore.java
+++ b/luni/src/main/java/java/security/KeyStore.java
@@ -159,7 +159,8 @@ public class KeyStore {
/**
* Returns a new instance of {@code KeyStore} from the specified provider
- * with the given type.
+ * with the given type. The {@code provider} supplied does not have to be
+ * registered.
*
* @param type
* the type of the returned {@code KeyStore}.
diff --git a/luni/src/main/java/java/security/MessageDigest.java b/luni/src/main/java/java/security/MessageDigest.java
index 658b41f..1d37a90 100644
--- a/luni/src/main/java/java/security/MessageDigest.java
+++ b/luni/src/main/java/java/security/MessageDigest.java
@@ -132,7 +132,8 @@ public abstract class MessageDigest extends MessageDigestSpi {
/**
* Returns a new instance of {@code MessageDigest} that utilizes the
- * specified algorithm from the specified provider.
+ * specified algorithm from the specified provider. The
+ * {@code provider} supplied does not have to be registered.
*
* @param algorithm
* the name of the algorithm to use
@@ -302,12 +303,12 @@ public abstract class MessageDigest extends MessageDigestSpi {
if (digesta.length != digestb.length) {
return false;
}
+ // Perform a constant time comparison to avoid timing attacks.
+ int v = 0;
for (int i = 0; i < digesta.length; i++) {
- if (digesta[i] != digestb[i]) {
- return false;
- }
+ v |= (digesta[i] ^ digestb[i]);
}
- return true;
+ return v == 0;
}
/**
@@ -351,14 +352,6 @@ public abstract class MessageDigest extends MessageDigestSpi {
}
}
- @Override
- public Object clone() throws CloneNotSupportedException {
- if (this instanceof Cloneable) {
- return super.clone();
- }
- throw new CloneNotSupportedException();
- }
-
/**
* Updates this {@code MessageDigest} using the given {@code input}.
*
@@ -420,12 +413,8 @@ public abstract class MessageDigest extends MessageDigestSpi {
// Returns a clone if the spiImpl is cloneable
@Override
public Object clone() throws CloneNotSupportedException {
- if (spiImpl instanceof Cloneable) {
- MessageDigestSpi spi = (MessageDigestSpi) spiImpl.clone();
- return new MessageDigestImpl(spi, getProvider(), getAlgorithm());
- }
-
- throw new CloneNotSupportedException();
+ MessageDigestSpi spi = (MessageDigestSpi) spiImpl.clone();
+ return new MessageDigestImpl(spi, getProvider(), getAlgorithm());
}
}
}
diff --git a/luni/src/main/java/java/security/Provider.java b/luni/src/main/java/java/security/Provider.java
index a6de19c..1704b58 100644
--- a/luni/src/main/java/java/security/Provider.java
+++ b/luni/src/main/java/java/security/Provider.java
@@ -804,6 +804,79 @@ public abstract class Provider extends Properties {
* provider it belongs and other properties.
*/
public static class Service {
+ /** Attribute name of supported key classes. */
+ private static final String ATTR_SUPPORTED_KEY_CLASSES = "SupportedKeyClasses";
+
+ /** Attribute name of supported key formats. */
+ private static final String ATTR_SUPPORTED_KEY_FORMATS = "SupportedKeyFormats";
+
+ /** Whether this type supports calls to {@link #supportsParameter(Object)}. */
+ private static final HashMap<String, Boolean> supportsParameterTypes
+ = new HashMap<String, Boolean>();
+ static {
+ // Does not support parameter
+ supportsParameterTypes.put("AlgorithmParameterGenerator", false);
+ supportsParameterTypes.put("AlgorithmParameters", false);
+ supportsParameterTypes.put("CertificateFactory", false);
+ supportsParameterTypes.put("CertPathBuilder", false);
+ supportsParameterTypes.put("CertPathValidator", false);
+ supportsParameterTypes.put("CertStore", false);
+ supportsParameterTypes.put("KeyFactory", false);
+ supportsParameterTypes.put("KeyGenerator", false);
+ supportsParameterTypes.put("KeyManagerFactory", false);
+ supportsParameterTypes.put("KeyPairGenerator", false);
+ supportsParameterTypes.put("KeyStore", false);
+ supportsParameterTypes.put("MessageDigest", false);
+ supportsParameterTypes.put("SecretKeyFactory", false);
+ supportsParameterTypes.put("SecureRandom", false);
+ supportsParameterTypes.put("SSLContext", false);
+ supportsParameterTypes.put("TrustManagerFactory", false);
+
+ // Supports parameter
+ supportsParameterTypes.put("Cipher", true);
+ supportsParameterTypes.put("KeyAgreement", true);
+ supportsParameterTypes.put("Mac", true);
+ supportsParameterTypes.put("Signature", true);
+ }
+
+ /** Constructor argument classes for calls to {@link #newInstance(Object)}. */
+ private static final HashMap<String, Class<?>> constructorParameterClasses = new HashMap<String, Class<?>>();
+ static {
+ // Types that take a parameter to newInstance
+ constructorParameterClasses.put("CertStore",
+ loadClassOrThrow("java.security.cert.CertStoreParameters"));
+
+ // Types that do not take any kind of parameter
+ constructorParameterClasses.put("AlgorithmParameterGenerator", null);
+ constructorParameterClasses.put("AlgorithmParameters", null);
+ constructorParameterClasses.put("CertificateFactory", null);
+ constructorParameterClasses.put("CertPathBuilder", null);
+ constructorParameterClasses.put("CertPathValidator", null);
+ constructorParameterClasses.put("KeyFactory", null);
+ constructorParameterClasses.put("KeyGenerator", null);
+ constructorParameterClasses.put("KeyManagerFactory", null);
+ constructorParameterClasses.put("KeyPairGenerator", null);
+ constructorParameterClasses.put("KeyStore", null);
+ constructorParameterClasses.put("MessageDigest", null);
+ constructorParameterClasses.put("SecretKeyFactory", null);
+ constructorParameterClasses.put("SecureRandom", null);
+ constructorParameterClasses.put("SSLContext", null);
+ constructorParameterClasses.put("TrustManagerFactory", null);
+ constructorParameterClasses.put("Cipher", null);
+ constructorParameterClasses.put("KeyAgreement", null);
+ constructorParameterClasses.put("Mac", null);
+ constructorParameterClasses.put("Signature", null);
+ }
+
+ /** Called to load a class if it's critical that the class exists. */
+ private static Class<?> loadClassOrThrow(String className) {
+ try {
+ return Provider.class.getClassLoader().loadClass(className);
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+
// The provider
private Provider provider;
@@ -828,6 +901,15 @@ public abstract class Provider extends Properties {
// For newInstance() optimization
private String lastClassName;
+ /** Indicates whether supportedKeyClasses and supportedKeyFormats. */
+ private volatile boolean supportedKeysInitialized;
+
+ /** List of classes that this service supports. */
+ private Class<?>[] keyClasses;
+
+ /** List of key formats this service supports. */
+ private String[] keyFormats;
+
/**
* Constructs a new instance of {@code Service} with the given
* attributes.
@@ -993,28 +1075,52 @@ public abstract class Provider extends Properties {
throw new NoSuchAlgorithmException(type + " " + algorithm + " implementation not found: " + e);
}
}
- if (constructorParameter == null) {
- try {
- return implementation.newInstance();
- } catch (Exception e) {
- throw new NoSuchAlgorithmException(
- type + " " + algorithm + " implementation not found", e);
+
+ // We don't know whether this takes a parameter or not.
+ if (!constructorParameterClasses.containsKey(type)) {
+ if (constructorParameter == null) {
+ return newInstanceNoParameter();
+ } else {
+ return newInstanceWithParameter(constructorParameter,
+ constructorParameter.getClass());
}
}
- if (!supportsParameter(constructorParameter)) {
- throw new InvalidParameterException(type + ": service cannot use the parameter");
+
+ // A known type, but it's not required to have a parameter even if a
+ // class is specified.
+ if (constructorParameter == null) {
+ return newInstanceNoParameter();
}
- Class[] parameterTypes = new Class[1];
- Object[] initargs = { constructorParameter };
+ // Make sure the provided constructor class is valid.
+ final Class<?> expectedClass = constructorParameterClasses.get(type);
+ if (expectedClass == null) {
+ throw new IllegalArgumentException("Constructor parameter not supported for "
+ + type);
+ }
+ if (!expectedClass.isAssignableFrom(constructorParameter.getClass())) {
+ throw new IllegalArgumentException("Expecting constructor parameter of type "
+ + expectedClass.getName() + " but was "
+ + constructorParameter.getClass().getName());
+ }
+ return newInstanceWithParameter(constructorParameter, expectedClass);
+ }
+
+ private Object newInstanceWithParameter(Object constructorParameter,
+ Class<?> parameterClass) throws NoSuchAlgorithmException {
try {
- if (type.equalsIgnoreCase("CertStore")) {
- parameterTypes[0] = Class.forName("java.security.cert.CertStoreParameters");
- } else {
- parameterTypes[0] = constructorParameter.getClass();
- }
- return implementation.getConstructor(parameterTypes)
- .newInstance(initargs);
+ Class<?>[] parameterTypes = { parameterClass };
+ Object[] initargs = { constructorParameter };
+ return implementation.getConstructor(parameterTypes).newInstance(initargs);
+ } catch (Exception e) {
+ throw new NoSuchAlgorithmException(type + " " + algorithm
+ + " implementation not found", e);
+ }
+ }
+
+ private Object newInstanceNoParameter() throws NoSuchAlgorithmException {
+ try {
+ return implementation.newInstance();
} catch (Exception e) {
throw new NoSuchAlgorithmException(type + " " + algorithm
+ " implementation not found", e);
@@ -1031,7 +1137,111 @@ public abstract class Provider extends Properties {
* constructor parameter, {@code false} otherwise.
*/
public boolean supportsParameter(Object parameter) {
- return true;
+ Boolean supportsParameter = supportsParameterTypes.get(type);
+ if (supportsParameter == null) {
+ return true;
+ }
+ if (!supportsParameter) {
+ throw new InvalidParameterException("Cannot use a parameter with " + type);
+ }
+
+ /*
+ * Only Key parameters are allowed, but allow null since there might
+ * not be any listed classes or formats for this instance.
+ */
+ if (parameter != null && !(parameter instanceof Key)) {
+ throw new InvalidParameterException("Parameter should be of type Key");
+ }
+
+ ensureSupportedKeysInitialized();
+
+ // No restriction specified by Provider registration.
+ if (keyClasses == null && keyFormats == null) {
+ return true;
+ }
+
+ // Restriction specified by registration, so null is not acceptable.
+ if (parameter == null) {
+ return false;
+ }
+
+ Key keyParam = (Key) parameter;
+ if (keyClasses != null && isInArray(keyClasses, keyParam.getClass())) {
+ return true;
+ }
+ if (keyFormats != null && isInArray(keyFormats, keyParam.getFormat())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Initialize the list of supported key classes and formats.
+ */
+ private void ensureSupportedKeysInitialized() {
+ if (supportedKeysInitialized) {
+ return;
+ }
+
+ final String supportedClassesString = getAttribute(ATTR_SUPPORTED_KEY_CLASSES);
+ if (supportedClassesString != null) {
+ String[] keyClassNames = supportedClassesString.split("\\|");
+ ArrayList<Class<?>> supportedClassList = new ArrayList<Class<?>>(
+ keyClassNames.length);
+ final ClassLoader classLoader = getProvider().getClass().getClassLoader();
+ for (String keyClassName : keyClassNames) {
+ try {
+ Class<?> keyClass = classLoader.loadClass(keyClassName);
+ if (Key.class.isAssignableFrom(keyClass)) {
+ supportedClassList.add(keyClass);
+ }
+ } catch (ClassNotFoundException ignored) {
+ }
+ }
+ keyClasses = supportedClassList.toArray(new Class<?>[supportedClassList.size()]);
+ }
+
+ final String supportedFormatString = getAttribute(ATTR_SUPPORTED_KEY_FORMATS);
+ if (supportedFormatString != null) {
+ keyFormats = supportedFormatString.split("\\|");
+ }
+
+ supportedKeysInitialized = true;
+ }
+
+ /**
+ * Check if an item is in the array. The array of supported key classes
+ * and formats is usually just a length of 1, so a simple array is
+ * faster than a Set.
+ */
+ private static <T> boolean isInArray(T[] itemList, T target) {
+ if (target == null) {
+ return false;
+ }
+ for (T item : itemList) {
+ if (target.equals(item)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if an item is in the array. The array of supported key classes
+ * and formats is usually just a length of 1, so a simple array is
+ * faster than a Set.
+ */
+ private static boolean isInArray(Class<?>[] itemList, Class<?> target) {
+ if (target == null) {
+ return false;
+ }
+ for (Class<?> item : itemList) {
+ if (item.isAssignableFrom(target)) {
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/luni/src/main/java/java/security/SecureRandom.java b/luni/src/main/java/java/security/SecureRandom.java
index 281885b..7a03801 100644
--- a/luni/src/main/java/java/security/SecureRandom.java
+++ b/luni/src/main/java/java/security/SecureRandom.java
@@ -46,8 +46,8 @@ import org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl;
* For deriving keys from passwords, see
* {@link javax.crypto.SecretKeyFactory}.
*
- * <h3><a name="insecure_seed">Seeding {@code SecureRandom} may be
- * insecure</a></h3>
+ * <h3><a name="insecure_seed"></a>Seeding {@code SecureRandom} may be
+ * insecure</h3>
* A seed is an array of bytes used to bootstrap random number generation.
* To produce cryptographically secure random numbers, both the seed and the
* algorithm must be secure.
@@ -190,7 +190,8 @@ public class SecureRandom extends Random {
/**
* Returns a new instance of {@code SecureRandom} that utilizes the
- * specified algorithm from the specified provider.
+ * specified algorithm from the specified provider. The
+ * {@code provider} supplied does not have to be registered.
*
* @param algorithm
* the name of the algorithm to use.
diff --git a/luni/src/main/java/java/security/Security.java b/luni/src/main/java/java/security/Security.java
index 0b6961b..b859f9a 100644
--- a/luni/src/main/java/java/security/Security.java
+++ b/luni/src/main/java/java/security/Security.java
@@ -19,6 +19,7 @@ package java.security;
import java.io.BufferedInputStream;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
@@ -72,10 +73,9 @@ public final class Security {
// Register default providers
private static void registerDefaultProviders() {
secprops.put("security.provider.1", "com.android.org.conscrypt.OpenSSLProvider");
- secprops.put("security.provider.2", "org.apache.harmony.security.provider.cert.DRLCertFactory");
- secprops.put("security.provider.3", "com.android.org.bouncycastle.jce.provider.BouncyCastleProvider");
- secprops.put("security.provider.4", "org.apache.harmony.security.provider.crypto.CryptoProvider");
- secprops.put("security.provider.5", "com.android.org.conscrypt.JSSEProvider");
+ secprops.put("security.provider.2", "com.android.org.bouncycastle.jce.provider.BouncyCastleProvider");
+ secprops.put("security.provider.3", "org.apache.harmony.security.provider.crypto.CryptoProvider");
+ secprops.put("security.provider.4", "com.android.org.conscrypt.JSSEProvider");
}
/**
@@ -96,7 +96,7 @@ public final class Security {
String prop = "Alg." + propName + "." + algName;
Provider[] providers = getProviders();
for (Provider provider : providers) {
- for (Enumeration e = provider.propertyNames(); e.hasMoreElements(); ) {
+ for (Enumeration<?> e = provider.propertyNames(); e.hasMoreElements();) {
String propertyName = (String) e.nextElement();
if (propertyName.equalsIgnoreCase(prop)) {
return provider.getProperty(propertyName);
@@ -184,7 +184,8 @@ public final class Security {
* @return an array containing all installed providers.
*/
public static synchronized Provider[] getProviders() {
- return Services.getProviders();
+ ArrayList<Provider> providers = Services.getProviders();
+ return providers.toArray(new Provider[providers.size()]);
}
/**
@@ -274,7 +275,7 @@ public final class Security {
if (filter.isEmpty()) {
return null;
}
- java.util.List<Provider> result = Services.getProvidersList();
+ ArrayList<Provider> result = new ArrayList<Provider>(Services.getProviders());
Set<Entry<String, String>> keys = filter.entrySet();
Map.Entry<String, String> entry;
for (Iterator<Entry<String, String>> it = keys.iterator(); it.hasNext();) {
@@ -306,18 +307,7 @@ public final class Security {
if (serv.length() == 0 || alg.length() == 0) {
throw new InvalidParameterException();
}
- Provider p;
- for (int k = 0; k < result.size(); k++) {
- try {
- p = result.get(k);
- } catch (IndexOutOfBoundsException e) {
- break;
- }
- if (!p.implementsAlg(serv, alg, attribute, val)) {
- result.remove(p);
- k--;
- }
- }
+ filterProviders(result, serv, alg, attribute, val);
}
if (result.size() > 0) {
return result.toArray(new Provider[result.size()]);
@@ -325,6 +315,17 @@ public final class Security {
return null;
}
+ private static void filterProviders(ArrayList<Provider> providers, String service,
+ String algorithm, String attribute, String attrValue) {
+ Iterator<Provider> it = providers.iterator();
+ while (it.hasNext()) {
+ Provider p = it.next();
+ if (!p.implementsAlg(service, algorithm, attribute, attrValue)) {
+ it.remove();
+ }
+ }
+ }
+
/**
* Returns the value of the security property named by the argument.
*
@@ -347,6 +348,7 @@ public final class Security {
* Sets the value of the specified security property.
*/
public static void setProperty(String key, String value) {
+ Services.setNeedRefresh();
secprops.put(key, value);
}
@@ -384,9 +386,9 @@ public final class Security {
*
*/
private static void renumProviders() {
- Provider[] p = Services.getProviders();
- for (int i = 0; i < p.length; i++) {
- p[i].setProviderNumber(i + 1);
+ ArrayList<Provider> providers = Services.getProviders();
+ for (int i = 0; i < providers.size(); i++) {
+ providers.get(i).setProviderNumber(i + 1);
}
}
diff --git a/luni/src/main/java/java/security/Signature.java b/luni/src/main/java/java/security/Signature.java
index 0372c33..a39d59b 100644
--- a/luni/src/main/java/java/security/Signature.java
+++ b/luni/src/main/java/java/security/Signature.java
@@ -21,10 +21,11 @@ import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import org.apache.harmony.security.fortress.Engine;
-
+import org.apache.harmony.security.fortress.Engine.SpiAndProvider;
/**
* {@code Signature} is an engine class which is capable of creating and
@@ -39,13 +40,13 @@ public abstract class Signature extends SignatureSpi {
private static final String SERVICE = "Signature";
// Used to access common engine functionality
- private static Engine ENGINE = new Engine(SERVICE);
+ private static final Engine ENGINE = new Engine(SERVICE);
// The provider
- private Provider provider;
+ Provider provider;
// The algorithm.
- private String algorithm;
+ final String algorithm;
/**
* Constant that indicates that this {@code Signature} instance has not yet
@@ -101,16 +102,7 @@ public abstract class Signature extends SignatureSpi {
if (algorithm == null) {
throw new NullPointerException("algorithm == null");
}
- Engine.SpiAndProvider sap = ENGINE.getInstance(algorithm, null);
- Object spi = sap.spi;
- Provider provider = sap.provider;
- if (spi instanceof Signature) {
- Signature result = (Signature) spi;
- result.algorithm = algorithm;
- result.provider = provider;
- return result;
- }
- return new SignatureImpl((SignatureSpi) spi, provider, algorithm);
+ return getSignature(algorithm, null);
}
/**
@@ -143,12 +135,13 @@ public abstract class Signature extends SignatureSpi {
if (p == null) {
throw new NoSuchProviderException(provider);
}
- return getSignatureInstance(algorithm, p);
+ return getSignature(algorithm, p);
}
/**
* Returns a new instance of {@code Signature} that utilizes the specified
- * algorithm from the specified provider.
+ * algorithm from the specified provider. The {@code provider} supplied
+ * does not have to be registered.
*
* @param algorithm
* the name of the algorithm to use.
@@ -170,19 +163,68 @@ public abstract class Signature extends SignatureSpi {
if (provider == null) {
throw new IllegalArgumentException("provider == null");
}
- return getSignatureInstance(algorithm, provider);
+ return getSignature(algorithm, provider);
+ }
+
+ private static Signature getSignature(String algorithm, Provider provider)
+ throws NoSuchAlgorithmException {
+ if (algorithm == null || algorithm.isEmpty()) {
+ throw new NoSuchAlgorithmException("Unknown algorithm: " + algorithm);
+ }
+
+ SpiAndProvider spiAndProvider = tryAlgorithm(null, provider, algorithm);
+ if (spiAndProvider == null) {
+ if (provider == null) {
+ throw new NoSuchAlgorithmException("No provider found for " + algorithm);
+ } else {
+ throw new NoSuchAlgorithmException("Provider " + provider.getName()
+ + " does not provide " + algorithm);
+ }
+ }
+ if (spiAndProvider.spi instanceof Signature) {
+ return (Signature) spiAndProvider.spi;
+ }
+ return new SignatureImpl(algorithm, provider);
+ }
+
+ private static Engine.SpiAndProvider tryAlgorithm(Key key, Provider provider, String algorithm) {
+ if (provider != null) {
+ Provider.Service service = provider.getService(SERVICE, algorithm);
+ if (service == null) {
+ return null;
+ }
+ return tryAlgorithmWithProvider(key, service);
+ }
+ ArrayList<Provider.Service> services = ENGINE.getServices(algorithm);
+ if (services == null) {
+ return null;
+ }
+ for (Provider.Service service : services) {
+ Engine.SpiAndProvider sap = tryAlgorithmWithProvider(key, service);
+ if (sap != null) {
+ return sap;
+ }
+ }
+ return null;
}
- private static Signature getSignatureInstance(String algorithm,
- Provider provider) throws NoSuchAlgorithmException {
- Object spi = ENGINE.getInstance(algorithm, provider, null);
- if (spi instanceof Signature) {
- Signature result = (Signature) spi;
- result.algorithm = algorithm;
- result.provider = provider;
- return result;
+ private static Engine.SpiAndProvider tryAlgorithmWithProvider(Key key, Provider.Service service) {
+ try {
+ if (key != null && !service.supportsParameter(key)) {
+ return null;
+ }
+
+ Engine.SpiAndProvider sap = ENGINE.getInstance(service, null);
+ if (sap.spi == null || sap.provider == null) {
+ return null;
+ }
+ if (!(sap.spi instanceof SignatureSpi)) {
+ return null;
+ }
+ return sap;
+ } catch (NoSuchAlgorithmException ignored) {
}
- return new SignatureImpl((SignatureSpi) spi, provider, algorithm);
+ return null;
}
/**
@@ -191,10 +233,18 @@ public abstract class Signature extends SignatureSpi {
* @return the provider associated with this {@code Signature}.
*/
public final Provider getProvider() {
+ ensureProviderChosen();
return provider;
}
/**
+ * This makes sure the provider is chosen since Signature is abstract and
+ * getProvider is final but we need to support late binding.
+ */
+ void ensureProviderChosen() {
+ }
+
+ /**
* Returns the name of the algorithm of this {@code Signature}.
*
* @return the name of the algorithm of this {@code Signature}.
@@ -237,10 +287,10 @@ public abstract class Signature extends SignatureSpi {
public final void initVerify(Certificate certificate)
throws InvalidKeyException {
if (certificate instanceof X509Certificate) {
- Set ce = ((X509Certificate) certificate).getCriticalExtensionOIDs();
+ Set<String> ce = ((X509Certificate) certificate).getCriticalExtensionOIDs();
boolean critical = false;
if (ce != null && !ce.isEmpty()) {
- for (Iterator i = ce.iterator(); i.hasNext();) {
+ for (Iterator<String> i = ce.iterator(); i.hasNext();) {
if ("2.5.29.15".equals(i.next())) {
//KeyUsage OID = 2.5.29.15
critical = true;
@@ -574,92 +624,115 @@ public abstract class Signature extends SignatureSpi {
return engineGetParameter(param);
}
- @Override
- public Object clone() throws CloneNotSupportedException {
- if (this instanceof Cloneable) {
- return super.clone();
- }
- throw new CloneNotSupportedException();
- }
-
/**
- *
* Internal Signature implementation
- *
*/
private static class SignatureImpl extends Signature {
+ /**
+ * Lock held while the SPI is initializing.
+ */
+ private final Object initLock = new Object();
+
+ // The provider specified when creating this instance.
+ private final Provider specifiedProvider;
+
private SignatureSpi spiImpl;
- // Constructor
- public SignatureImpl(SignatureSpi signatureSpi, Provider provider,
- String algorithm) {
+ public SignatureImpl(String algorithm, Provider provider) {
super(algorithm);
- super.provider = provider;
- spiImpl = signatureSpi;
+ this.specifiedProvider = provider;
+ }
+
+ private SignatureImpl(String algorithm, Provider provider, SignatureSpi spi) {
+ this(algorithm, provider);
+ spiImpl = spi;
+ }
+
+ @Override
+ void ensureProviderChosen() {
+ getSpi(null);
}
- // engineSign() implementation
@Override
protected byte[] engineSign() throws SignatureException {
- return spiImpl.engineSign();
+ return getSpi().engineSign();
}
- // engineUpdate() implementation
@Override
protected void engineUpdate(byte arg0) throws SignatureException {
- spiImpl.engineUpdate(arg0);
+ getSpi().engineUpdate(arg0);
}
- // engineVerify() implementation
@Override
protected boolean engineVerify(byte[] arg0) throws SignatureException {
- return spiImpl.engineVerify(arg0);
+ return getSpi().engineVerify(arg0);
}
- // engineUpdate() implementation
@Override
- protected void engineUpdate(byte[] arg0, int arg1, int arg2)
- throws SignatureException {
- spiImpl.engineUpdate(arg0, arg1, arg2);
+ protected void engineUpdate(byte[] arg0, int arg1, int arg2) throws SignatureException {
+ getSpi().engineUpdate(arg0, arg1, arg2);
}
- // engineInitSign() implementation
@Override
- protected void engineInitSign(PrivateKey arg0)
- throws InvalidKeyException {
- spiImpl.engineInitSign(arg0);
+ protected void engineInitSign(PrivateKey arg0) throws InvalidKeyException {
+ getSpi(arg0).engineInitSign(arg0);
}
- // engineInitVerify() implementation
@Override
- protected void engineInitVerify(PublicKey arg0)
- throws InvalidKeyException {
- spiImpl.engineInitVerify(arg0);
+ protected void engineInitVerify(PublicKey arg0) throws InvalidKeyException {
+ getSpi(arg0).engineInitVerify(arg0);
}
- // engineGetParameter() implementation
@Override
- protected Object engineGetParameter(String arg0)
- throws InvalidParameterException {
- return spiImpl.engineGetParameter(arg0);
+ protected Object engineGetParameter(String arg0) throws InvalidParameterException {
+ return getSpi().engineGetParameter(arg0);
}
- // engineSetParameter() implementation
@Override
protected void engineSetParameter(String arg0, Object arg1)
throws InvalidParameterException {
- spiImpl.engineSetParameter(arg0, arg1);
+ getSpi().engineSetParameter(arg0, arg1);
+ }
+
+ @Override
+ protected void engineSetParameter(AlgorithmParameterSpec arg0)
+ throws InvalidAlgorithmParameterException {
+ getSpi().engineSetParameter(arg0);
}
- // Returns a clone if the spiImpl is cloneable
@Override
public Object clone() throws CloneNotSupportedException {
- if (spiImpl instanceof Cloneable) {
- SignatureSpi spi = (SignatureSpi) spiImpl.clone();
- return new SignatureImpl(spi, getProvider(), getAlgorithm());
+ SignatureSpi spi = spiImpl != null ? (SignatureSpi) spiImpl.clone() : null;
+ return new SignatureImpl(getAlgorithm(), getProvider(), spi);
+ }
+
+ /**
+ * Makes sure a CipherSpi that matches this type is selected.
+ */
+ private SignatureSpi getSpi(Key key) {
+ synchronized (initLock) {
+ if (spiImpl != null && key == null) {
+ return spiImpl;
+ }
+
+ final Engine.SpiAndProvider sap = tryAlgorithm(key, specifiedProvider, algorithm);
+ if (sap == null) {
+ throw new ProviderException("No provider for " + getAlgorithm());
+ }
+
+ spiImpl = (SignatureSpi) sap.spi;
+ provider = sap.provider;
+
+ return spiImpl;
}
- throw new CloneNotSupportedException();
+ }
+
+ /**
+ * Convenience call when the Key is not available.
+ */
+ private SignatureSpi getSpi() {
+ return getSpi(null);
}
}
}
diff --git a/luni/src/main/java/java/security/SignatureSpi.java b/luni/src/main/java/java/security/SignatureSpi.java
index 27be30c..66c43d7 100644
--- a/luni/src/main/java/java/security/SignatureSpi.java
+++ b/luni/src/main/java/java/security/SignatureSpi.java
@@ -307,9 +307,6 @@ public abstract class SignatureSpi {
@Override
public Object clone() throws CloneNotSupportedException {
- if (this instanceof Cloneable) {
- return super.clone();
- }
- throw new CloneNotSupportedException();
+ return super.clone();
}
}
diff --git a/luni/src/main/java/java/security/cert/CRLReason.java b/luni/src/main/java/java/security/cert/CRLReason.java
new file mode 100644
index 0000000..6f663db
--- /dev/null
+++ b/luni/src/main/java/java/security/cert/CRLReason.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.security.cert;
+
+import java.io.Serializable;
+
+/**
+ * The reason a CRL entry was revoked. See <a
+ * href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280</a> for more information.
+ *
+ * @since 1.7
+ * @hide
+ */
+public enum CRLReason implements Comparable<CRLReason>, Serializable {
+ UNSPECIFIED,
+ KEY_COMPROMISE,
+ CA_COMPROMISE,
+ AFFILIATION_CHANGED,
+ SUPERSEDED,
+ CESSATION_OF_OPERATION,
+ CERTIFICATE_HOLD,
+ UNUSED,
+ REMOVE_FROM_CRL,
+ PRIVILEGE_WITHDRAWN,
+ AA_COMPROMISE;
+}
diff --git a/luni/src/main/java/java/security/cert/CertPathValidator.java b/luni/src/main/java/java/security/cert/CertPathValidator.java
index a3a666a..962f530 100644
--- a/luni/src/main/java/java/security/cert/CertPathValidator.java
+++ b/luni/src/main/java/java/security/cert/CertPathValidator.java
@@ -140,7 +140,8 @@ public class CertPathValidator {
/**
* Returns a new certification path validator for the specified algorithm
- * from the specified provider.
+ * from the specified provider. The {@code provider} supplied does not
+ * have to be registered.
*
* @param algorithm
* the algorithm name.
diff --git a/luni/src/main/java/java/security/cert/CertStore.java b/luni/src/main/java/java/security/cert/CertStore.java
index 72d356f..606c93b 100644
--- a/luni/src/main/java/java/security/cert/CertStore.java
+++ b/luni/src/main/java/java/security/cert/CertStore.java
@@ -151,7 +151,9 @@ public class CertStore {
/**
* Creates a new {@code CertStore} instance from the specified provider with
- * the specified type and initialized with the specified parameters.
+ * the specified type and initialized with the specified parameters. The
+ * {@code provider} supplied does not have to be registered.
+ *
* @param type
* the certificate store type.
* @param params
diff --git a/luni/src/main/java/java/security/cert/Certificate.java b/luni/src/main/java/java/security/cert/Certificate.java
index 15c1dbe..675cf9f 100644
--- a/luni/src/main/java/java/security/cert/Certificate.java
+++ b/luni/src/main/java/java/security/cert/Certificate.java
@@ -152,15 +152,15 @@ public abstract class Certificate implements Serializable {
* performed.
* @param sigProvider
* String the name of the signature provider.
- * @exception CertificateException
+ * @throws CertificateException
* if encoding errors are detected.
- * @exception NoSuchAlgorithmException
+ * @throws NoSuchAlgorithmException
* if an unsupported algorithm is detected.
- * @exception InvalidKeyException
+ * @throws InvalidKeyException
* if an invalid key is detected.
- * @exception NoSuchProviderException
+ * @throws NoSuchProviderException
* if the specified provider does not exists.
- * @exception SignatureException
+ * @throws SignatureException
* if signature errors are detected.
*/
public abstract void verify(PublicKey key, String sigProvider)
diff --git a/luni/src/main/java/java/security/cert/CertificateFactory.java b/luni/src/main/java/java/security/cert/CertificateFactory.java
index f882d52..2805c0e 100644
--- a/luni/src/main/java/java/security/cert/CertificateFactory.java
+++ b/luni/src/main/java/java/security/cert/CertificateFactory.java
@@ -128,7 +128,8 @@ public class CertificateFactory {
/**
* Creates a new {@code CertificateFactory} instance from the specified
- * provider that provides the requested certificate type.
+ * provider that provides the requested certificate type. The
+ * {@code provider} supplied does not have to be registered.
*
* @param type
* the certificate type.
@@ -280,7 +281,7 @@ public class CertificateFactory {
* @param inStream
* the stream from where data is read to create the CRL.
* @return an initialized CRL.
- * @exception CRLException
+ * @throws CRLException
* if parsing problems are detected.
*/
public final CRL generateCRL(InputStream inStream) throws CRLException {
@@ -294,7 +295,7 @@ public class CertificateFactory {
* @param inStream
* the stream from which the data is read to create the CRLs.
* @return an initialized collection of CRLs.
- * @exception CRLException
+ * @throws CRLException
* if parsing problems are detected.
*/
public final Collection<? extends CRL> generateCRLs(InputStream inStream)
diff --git a/luni/src/main/java/java/security/cert/CertificateFactorySpi.java b/luni/src/main/java/java/security/cert/CertificateFactorySpi.java
index 117ef65..d157d9c 100644
--- a/luni/src/main/java/java/security/cert/CertificateFactorySpi.java
+++ b/luni/src/main/java/java/security/cert/CertificateFactorySpi.java
@@ -44,7 +44,7 @@ public abstract class CertificateFactorySpi {
* the stream from which the data is read to create the
* certificate.
* @return an initialized certificate.
- * @exception CertificateException
+ * @throws CertificateException
* if parsing problems are detected.
*/
public abstract Certificate engineGenerateCertificate(InputStream inStream)
@@ -57,7 +57,7 @@ public abstract class CertificateFactorySpi {
* @param inStream
* the stream from where data is read to create the certificates.
* @return a collection of certificates.
- * @exception CertificateException
+ * @throws CertificateException
* if parsing problems are detected.
*/
public abstract Collection<? extends Certificate>
@@ -70,7 +70,7 @@ public abstract class CertificateFactorySpi {
* @param inStream
* the stream from where data is read to create the CRL.
* @return an CRL instance.
- * @exception CRLException
+ * @throws CRLException
* if parsing problems are detected.
*/
public abstract CRL engineGenerateCRL(InputStream inStream)
@@ -83,7 +83,7 @@ public abstract class CertificateFactorySpi {
* @param inStream
* the stream from which the data is read to create the CRLs.
* @return a collection of CRLs.
- * @exception CRLException
+ * @throws CRLException
* if parsing problems are detected.
*/
public abstract Collection<? extends CRL>
diff --git a/luni/src/main/java/java/security/cert/CertificateRevokedException.java b/luni/src/main/java/java/security/cert/CertificateRevokedException.java
new file mode 100644
index 0000000..4d88a4d
--- /dev/null
+++ b/luni/src/main/java/java/security/cert/CertificateRevokedException.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.security.cert;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import javax.security.auth.x500.X500Principal;
+import org.apache.harmony.security.x509.InvalidityDate;
+
+/**
+ * Exception that is thrown when a certificate is invalid because it has been
+ * revoked.
+ *
+ * @since 1.7
+ * @hide
+ */
+public class CertificateRevokedException extends CertificateException {
+
+ private static final long serialVersionUID = 7839996631571608627L;
+
+ private final Date revocationDate;
+
+ private final CRLReason reason;
+
+ private final X500Principal authority;
+
+ // Maps may not be serializable, so serialize it manually.
+ private transient Map<String, Extension> extensions;
+
+ /**
+ * @param revocationDate date the certificate was revoked
+ * @param reason reason the certificate was revoked if available
+ * @param authority authority that revoked the certificate
+ * @param extensions X.509 extensions associated with this revocation
+ */
+ public CertificateRevokedException(Date revocationDate, CRLReason reason,
+ X500Principal authority, Map<String, Extension> extensions) {
+ this.revocationDate = revocationDate;
+ this.reason = reason;
+ this.authority = authority;
+ this.extensions = extensions;
+ }
+
+ /**
+ * Returns the principal of the authority that issued the revocation.
+ */
+ public X500Principal getAuthorityName() {
+ return authority;
+ }
+
+ /**
+ * X.509 extensions that are associated with this revocation.
+ */
+ public Map<String, Extension> getExtensions() {
+ return Collections.unmodifiableMap(extensions);
+ }
+
+ /**
+ * Returns the date when the certificate was known to become invalid if
+ * available.
+ */
+ public Date getInvalidityDate() {
+ if (extensions == null) {
+ return null;
+ }
+
+ Extension invalidityDateExtension = extensions.get("2.5.29.24");
+ if (invalidityDateExtension == null) {
+ return null;
+ }
+
+ try {
+ InvalidityDate invalidityDate = new InvalidityDate(invalidityDateExtension.getValue());
+ return invalidityDate.getDate();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the detail message of the thrown exception.
+ */
+ public String getMessage() {
+ StringBuffer sb = new StringBuffer("Certificate was revoked");
+ if (revocationDate != null) {
+ sb.append(" on ").append(revocationDate.toString());
+ }
+ if (reason != null) {
+ sb.append(" due to ").append(reason);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns the date the certificate was revoked.
+ */
+ public Date getRevocationDate() {
+ return (Date) revocationDate.clone();
+ }
+
+ /**
+ * Returns the reason the certificate was revoked if available.
+ */
+ public CRLReason getRevocationReason() {
+ return reason;
+ }
+
+ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+ stream.defaultReadObject();
+
+ int size = stream.readInt();
+ extensions = new HashMap<String, Extension>(size);
+ for (int i = 0; i < size; i++) {
+ String oid = (String) stream.readObject();
+ boolean critical = stream.readBoolean();
+ int valueLen = stream.readInt();
+ byte[] value = new byte[valueLen];
+ stream.read(value);
+ extensions.put(oid,
+ new org.apache.harmony.security.x509.Extension(oid, critical, value));
+ }
+ }
+
+ private void writeObject(ObjectOutputStream stream) throws IOException {
+ stream.defaultWriteObject();
+
+ stream.writeInt(extensions.size());
+ for (Extension e : extensions.values()) {
+ stream.writeObject(e.getId());
+ stream.writeBoolean(e.isCritical());
+ byte[] value = e.getValue();
+ stream.writeInt(value.length);
+ stream.write(value);
+ }
+ }
+}
diff --git a/luni/src/main/java/java/security/cert/Extension.java b/luni/src/main/java/java/security/cert/Extension.java
new file mode 100644
index 0000000..8013809
--- /dev/null
+++ b/luni/src/main/java/java/security/cert/Extension.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.security.cert;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * The Extension part of an X.509 certificate (as specified in <a
+ * href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280 &mdash; Internet X.509
+ * Public Key Infrastructure: Certificate and Certificate Revocation List (CRL)
+ * Profile</p>):
+ *
+ * <pre>
+ * Extension ::= SEQUENCE {
+ * extnID OBJECT IDENTIFIER,
+ * critical BOOLEAN DEFAULT FALSE,
+ * extnValue OCTET STRING
+ * }
+ * </pre>
+ *
+ * @since 1.7
+ * @hide
+ */
+public interface Extension {
+ /**
+ * Returns the OID (Object Identifier) for this extension encoded as a
+ * string (e.g., "2.5.29.15").
+ */
+ String getId();
+
+ /**
+ * Returns {@code true} if this extension is critical. If this is true and
+ * an implementation does not understand this extension, it must reject it.
+ * See RFC 3280 section 4.2 for more information.
+ */
+ boolean isCritical();
+
+ /**
+ * The DER-encoded value of this extension.
+ */
+ byte[] getValue();
+
+ /**
+ * Writes the DER-encoded extension to {@code out}.
+ *
+ * @throws IOException when there is an encoding error or error writing to
+ * {@code out}
+ */
+ void encode(OutputStream out) throws IOException;
+}
diff --git a/luni/src/main/java/java/security/cert/X509CRLEntry.java b/luni/src/main/java/java/security/cert/X509CRLEntry.java
index 4b4c15d..451cfd5 100644
--- a/luni/src/main/java/java/security/cert/X509CRLEntry.java
+++ b/luni/src/main/java/java/security/cert/X509CRLEntry.java
@@ -17,10 +17,13 @@
package java.security.cert;
+import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Date;
import javax.security.auth.x500.X500Principal;
+import org.apache.harmony.security.asn1.ASN1OctetString;
+import org.apache.harmony.security.x509.ReasonCode;
/**
* Abstract base class for entries in a certificate revocation list (CRL).
@@ -121,5 +124,25 @@ public abstract class X509CRLEntry implements X509Extension {
* @return a string representation of this instance.
*/
public abstract String toString();
-}
+ /**
+ * Returns the reason this CRL entry was revoked. If the implementation
+ * doesn't support reasons, this will return {@code null}.
+ *
+ * @since 1.7
+ * @hide
+ */
+ public CRLReason getRevocationReason() {
+ byte[] reasonBytes = getExtensionValue("2.5.29.21");
+ if (reasonBytes == null) {
+ return null;
+ }
+
+ try {
+ byte[] rawBytes = (byte[]) ASN1OctetString.getInstance().decode(reasonBytes);
+ return new ReasonCode(rawBytes).getReason();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/luni/src/main/java/java/security/interfaces/DSAParams.java b/luni/src/main/java/java/security/interfaces/DSAParams.java
index ed1fcb5..64ddb01 100644
--- a/luni/src/main/java/java/security/interfaces/DSAParams.java
+++ b/luni/src/main/java/java/security/interfaces/DSAParams.java
@@ -39,9 +39,9 @@ public interface DSAParams {
public BigInteger getP();
/**
- * Returns the subprime ({@code q} value.
+ * Returns the subprime ({@code q}) value.
*
- * @return the subprime ({@code q} value.
+ * @return the subprime ({@code q}) value.
*/
public BigInteger getQ();
diff --git a/luni/src/main/java/java/security/security.properties b/luni/src/main/java/java/security/security.properties
index dd5830d..a06283b 100644
--- a/luni/src/main/java/java/security/security.properties
+++ b/luni/src/main/java/java/security/security.properties
@@ -20,13 +20,11 @@
#
# Android's provider of OpenSSL backed implementations
security.provider.1=com.android.org.conscrypt.OpenSSLProvider
-# Favor Harmony's CertificateFactory.X509 over BouncyCastle's
-security.provider.2=org.apache.harmony.security.provider.cert.DRLCertFactory
# Android's stripped down BouncyCastle provider
-security.provider.3=com.android.org.bouncycastle.jce.provider.BouncyCastleProvider
+security.provider.2=com.android.org.bouncycastle.jce.provider.BouncyCastleProvider
# Remaining Harmony providers
-security.provider.4=org.apache.harmony.security.provider.crypto.CryptoProvider
-security.provider.5=com.android.org.conscrypt.JSSEProvider
+security.provider.3=org.apache.harmony.security.provider.crypto.CryptoProvider
+security.provider.4=com.android.org.conscrypt.JSSEProvider
diff --git a/luni/src/main/java/java/security/spec/ECParameterSpec.java b/luni/src/main/java/java/security/spec/ECParameterSpec.java
index 9860ac0..37b39ac 100644
--- a/luni/src/main/java/java/security/spec/ECParameterSpec.java
+++ b/luni/src/main/java/java/security/spec/ECParameterSpec.java
@@ -32,7 +32,7 @@ public class ECParameterSpec implements AlgorithmParameterSpec {
// Cofactor
private final int cofactor;
// Name of curve if available.
- private final String curveName;
+ private String curveName;
/**
* Creates a new {@code ECParameterSpec} with the specified elliptic curve,
@@ -52,23 +52,10 @@ public class ECParameterSpec implements AlgorithmParameterSpec {
*/
public ECParameterSpec(EllipticCurve curve, ECPoint generator,
BigInteger order, int cofactor) {
- this(curve, generator, order, cofactor, null);
- }
-
- /**
- * Creates a new {@code ECParameterSpec} with the specified named curve
- * and all of its parameters.
- *
- * @see #ECParameterSpec(EllipticCurve, ECPoint, BigInteger, int)
- * @hide
- */
- public ECParameterSpec(EllipticCurve curve, ECPoint generator,
- BigInteger order, int cofactor, String curveName) {
this.curve = curve;
this.generator = generator;
this.order = order;
this.cofactor = cofactor;
- this.curveName = curveName;
// throw NullPointerException if curve, generator or order is null
if (this.curve == null) {
throw new NullPointerException("curve == null");
@@ -125,6 +112,15 @@ public class ECParameterSpec implements AlgorithmParameterSpec {
}
/**
+ * Used to set the curve name if available.
+ *
+ * @hide
+ */
+ public void setCurveName(String curveName) {
+ this.curveName = curveName;
+ }
+
+ /**
* Returns the name of the curve if this is a named curve. Returns
* {@code null} if this is not known to be a named curve.
*
diff --git a/luni/src/main/java/java/sql/Date.java b/luni/src/main/java/java/sql/Date.java
index 4aac326..206d885 100644
--- a/luni/src/main/java/java/sql/Date.java
+++ b/luni/src/main/java/java/sql/Date.java
@@ -214,23 +214,19 @@ public class Date extends java.util.Date {
if (dateString == null) {
throw new IllegalArgumentException("dateString == null");
}
- int firstIndex = dateString.indexOf('-');
- int secondIndex = dateString.indexOf('-', firstIndex + 1);
- // secondIndex == -1 means none or only one separator '-' has been
- // found.
- // The string is separated into three parts by two separator characters,
- // if the first or the third part is null string, we should throw
- // IllegalArgumentException to follow RI
- if (secondIndex == -1 || firstIndex == 0
- || secondIndex + 1 == dateString.length()) {
+ if (dateString.length() > 10) {
+ // early fail to avoid parsing huge invalid strings
throw new IllegalArgumentException();
}
- // parse each part of the string
- int year = Integer.parseInt(dateString.substring(0, firstIndex));
- int month = Integer.parseInt(dateString.substring(firstIndex + 1,
- secondIndex));
- int day = Integer.parseInt(dateString.substring(secondIndex + 1,
- dateString.length()));
+
+ String[] parts = dateString.split("-");
+ if (parts.length != 3) {
+ throw new IllegalArgumentException();
+ }
+
+ int year = Integer.parsePositiveInt(parts[0]);
+ int month = Integer.parsePositiveInt(parts[1]);
+ int day = Integer.parsePositiveInt(parts[2]);
return new Date(year - 1900, month - 1, day);
}
diff --git a/luni/src/main/java/java/sql/Timestamp.java b/luni/src/main/java/java/sql/Timestamp.java
index f35fa9b..a45a2b8 100644
--- a/luni/src/main/java/java/sql/Timestamp.java
+++ b/luni/src/main/java/java/sql/Timestamp.java
@@ -408,7 +408,7 @@ public class Timestamp extends Date {
throw new IllegalArgumentException("Argument cannot be null");
}
- // omit trailing whitespace
+ // Omit trailing whitespace
s = s.trim();
if (!Pattern.matches(TIME_FORMAT_REGEX, s)) {
throw badTimestampString(s);
@@ -424,14 +424,14 @@ public class Timestamp extends Date {
* with the ParsePosition indicating the index of the "." which should
* precede the nanoseconds value
*/
- Date theDate;
+ Date date;
try {
- theDate = df.parse(s, pp);
+ date = df.parse(s, pp);
} catch (Exception e) {
throw badTimestampString(s);
}
- if (theDate == null) {
+ if (date == null) {
throw badTimestampString(s);
}
@@ -445,66 +445,38 @@ public class Timestamp extends Date {
*/
int position = pp.getIndex();
int remaining = s.length() - position;
- int theNanos;
+ int nanos;
if (remaining == 0) {
// First, allow for the case where no fraction of a second is given:
- theNanos = 0;
+ nanos = 0;
} else {
- /*
- * Case where fraction of a second is specified: Require 1 character
- * plus the "." in the remaining part of the string...
- */
- if ((s.length() - position) < ".n".length()) {
+ // Validate the string is in the range ".0" to ".999999999"
+ if (remaining < 2 || remaining > 10 || s.charAt(position) != '.') {
throw badTimestampString(s);
}
-
- /*
- * If we're strict, we should not allow any EXTRA characters after
- * the 9 digits
- */
- if ((s.length() - position) > ".nnnnnnnnn".length()) {
- throw badTimestampString(s);
- }
-
- // Require the next character to be a "."
- if (s.charAt(position) != '.') {
- throw new NumberFormatException("Bad input string format: expected '.' not '" +
- s.charAt(position) + "' in \"" + s + "\"");
- }
- // Get the length of the number string - need to account for the '.'
- int nanoLength = s.length() - position - 1;
-
- // Get the 9 characters following the "." as an integer
- String theNanoString = s.substring(position + 1, position + 1
- + nanoLength);
- /*
- * We must adjust for the cases where the nanos String was not 9
- * characters long by padding out with zeros
- */
- theNanoString = theNanoString + "000000000";
- theNanoString = theNanoString.substring(0, 9);
-
try {
- theNanos = Integer.parseInt(theNanoString);
- } catch (Exception e) {
- // If we get here, the string was not a number
+ nanos = Integer.parsePositiveInt(s.substring(position + 1));
+ } catch (NumberFormatException e) {
throw badTimestampString(s);
}
+ // We must adjust for the cases where the nanos String was not 9
+ // characters long (i.e. ".123" means 123000000 nanos)
+ if (nanos != 0) {
+ for (int i = remaining - 1; i < 9; i++) {
+ nanos *= 10;
+ }
+ }
}
- if (theNanos < 0 || theNanos > 999999999) {
- throw badTimestampString(s);
- }
-
- Timestamp theTimestamp = new Timestamp(theDate.getTime());
- theTimestamp.setNanos(theNanos);
+ Timestamp timestamp = new Timestamp(date.getTime());
+ timestamp.setNanos(nanos);
- return theTimestamp;
+ return timestamp;
}
private static IllegalArgumentException badTimestampString(String s) {
- throw new IllegalArgumentException("Timestamp format must be " +
+ return new IllegalArgumentException("Timestamp format must be " +
"yyyy-MM-dd HH:mm:ss.fffffffff; was '" + s + "'");
}
}
diff --git a/luni/src/main/java/java/text/Bidi.java b/luni/src/main/java/java/text/Bidi.java
index e4b30e6..d73ea4a 100644
--- a/luni/src/main/java/java/text/Bidi.java
+++ b/luni/src/main/java/java/text/Bidi.java
@@ -421,28 +421,20 @@ public final class Bidi {
/**
* Returns the base level.
- *
- * @return the base level.
*/
public int getBaseLevel() {
return baseLevel;
}
/**
- * Returns the length of the text in the {@code Bidi} object.
- *
- * @return the length.
+ * Returns the length of the text.
*/
public int getLength() {
return length;
}
/**
- * Returns the level of a specified character.
- *
- * @param offset
- * the offset of the character.
- * @return the level.
+ * Returns the level of the given character.
*/
public int getLevelAt(int offset) {
try {
@@ -453,74 +445,51 @@ public final class Bidi {
}
/**
- * Returns the number of runs in the bidirectional text.
- *
- * @return the number of runs, at least 1.
+ * Returns the number of runs in the text, at least 1.
*/
public int getRunCount() {
return unidirectional ? 1 : runs.length;
}
/**
- * Returns the level of the specified run.
- *
- * @param run
- * the index of the run.
- * @return the level of the run.
+ * Returns the level of the given run.
*/
public int getRunLevel(int run) {
return unidirectional ? baseLevel : runs[run].getLevel();
}
/**
- * Returns the limit offset of the specified run.
- *
- * @param run
- * the index of the run.
- * @return the limit offset of the run.
+ * Returns the limit offset of the given run.
*/
public int getRunLimit(int run) {
return unidirectional ? length : runs[run].getLimit();
}
/**
- * Returns the start offset of the specified run.
- *
- * @param run
- * the index of the run.
- * @return the start offset of the run.
+ * Returns the start offset of the given run.
*/
public int getRunStart(int run) {
return unidirectional ? 0 : runs[run].getStart();
}
/**
- * Indicates whether the text is from left to right, that is, both the base
+ * Returns true if the text is from left to right, that is, both the base
* direction and the text direction is from left to right.
- *
- * @return {@code true} if the text is from left to right; {@code false}
- * otherwise.
*/
public boolean isLeftToRight() {
return direction == UBiDiDirection_UBIDI_LTR;
}
/**
- * Indicates whether the text direction is mixed.
- *
- * @return {@code true} if the text direction is mixed; {@code false}
- * otherwise.
+ * Returns true if the text direction is mixed.
*/
public boolean isMixed() {
return direction == UBiDiDirection_UBIDI_MIXED;
}
/**
- * Indicates whether the text is from right to left, that is, both the base
+ * Returns true if the text is from right to left, that is, both the base
* direction and the text direction is from right to left.
- *
- * @return {@code true} if the text is from right to left; {@code false}
- * otherwise.
*/
public boolean isRightToLeft() {
return direction == UBiDiDirection_UBIDI_RTL;
diff --git a/luni/src/main/java/java/text/BreakIterator.java b/luni/src/main/java/java/text/BreakIterator.java
index b14647c..81545b2 100644
--- a/luni/src/main/java/java/text/BreakIterator.java
+++ b/luni/src/main/java/java/text/BreakIterator.java
@@ -268,13 +268,9 @@ public abstract class BreakIterator implements Cloneable {
/**
* Returns a new instance of {@code BreakIterator} to iterate over
* characters using the given locale.
- *
- * @param where
- * the given locale.
- * @return a new instance of {@code BreakIterator} using the given locale.
*/
- public static BreakIterator getCharacterInstance(Locale where) {
- return new RuleBasedBreakIterator(NativeBreakIterator.getCharacterInstance(where));
+ public static BreakIterator getCharacterInstance(Locale locale) {
+ return new RuleBasedBreakIterator(NativeBreakIterator.getCharacterInstance(locale));
}
/**
@@ -290,14 +286,9 @@ public abstract class BreakIterator implements Cloneable {
/**
* Returns a new instance of {@code BreakIterator} to iterate over
* line breaks using the given locale.
- *
- * @param where
- * the given locale.
- * @return a new instance of {@code BreakIterator} using the given locale.
- * @throws NullPointerException if {@code where} is {@code null}.
*/
- public static BreakIterator getLineInstance(Locale where) {
- return new RuleBasedBreakIterator(NativeBreakIterator.getLineInstance(where));
+ public static BreakIterator getLineInstance(Locale locale) {
+ return new RuleBasedBreakIterator(NativeBreakIterator.getLineInstance(locale));
}
/**
@@ -313,14 +304,9 @@ public abstract class BreakIterator implements Cloneable {
/**
* Returns a new instance of {@code BreakIterator} to iterate over
* sentence-breaks using the given locale.
- *
- * @param where
- * the given locale.
- * @return a new instance of {@code BreakIterator} using the given locale.
- * @throws NullPointerException if {@code where} is {@code null}.
*/
- public static BreakIterator getSentenceInstance(Locale where) {
- return new RuleBasedBreakIterator(NativeBreakIterator.getSentenceInstance(where));
+ public static BreakIterator getSentenceInstance(Locale locale) {
+ return new RuleBasedBreakIterator(NativeBreakIterator.getSentenceInstance(locale));
}
/**
@@ -336,14 +322,9 @@ public abstract class BreakIterator implements Cloneable {
/**
* Returns a new instance of {@code BreakIterator} to iterate over
* word-breaks using the given locale.
- *
- * @param where
- * the given locale.
- * @return a new instance of {@code BreakIterator} using the given locale.
- * @throws NullPointerException if {@code where} is {@code null}.
*/
- public static BreakIterator getWordInstance(Locale where) {
- return new RuleBasedBreakIterator(NativeBreakIterator.getWordInstance(where));
+ public static BreakIterator getWordInstance(Locale locale) {
+ return new RuleBasedBreakIterator(NativeBreakIterator.getWordInstance(locale));
}
/**
diff --git a/luni/src/main/java/java/text/CollationElementIterator.java b/luni/src/main/java/java/text/CollationElementIterator.java
index 5da3b65..4f75a9a 100644
--- a/luni/src/main/java/java/text/CollationElementIterator.java
+++ b/luni/src/main/java/java/text/CollationElementIterator.java
@@ -43,6 +43,11 @@ import libcore.icu.CollationElementIteratorICU;
* "&#92;u0086b": the first collation element is collation_element('a'), the
* second one is collation_element('e'), and the third collation element is
* collation_element('b').
+ *
+ * <p>Note that calls to {@code next} and {@code previous} can not be mixed.
+ * To change iteration direction, {@code reset}, {@code setOffset} or {@code setText}
+ * must be called to reset the iterator. If a change of direction is done without one
+ * of these calls, the result is undefined.
*/
public final class CollationElementIterator {
@@ -61,7 +66,7 @@ public final class CollationElementIterator {
}
/**
- * Obtains the maximum length of any expansion sequence that ends with the
+ * Returns the maximum length of any expansion sequence that ends with the
* specified collation element. Returns {@code 1} if there is no expansion
* with this collation element as the last element.
*
@@ -69,15 +74,13 @@ public final class CollationElementIterator {
* a collation element that has been previously obtained from a
* call to either the {@link #next()} or {@link #previous()}
* method.
- * @return the maximum length of any expansion sequence ending with the
- * specified collation element.
*/
public int getMaxExpansion(int order) {
return this.icuIterator.getMaxExpansion(order);
}
/**
- * Obtains the character offset in the source string corresponding to the
+ * Returns the character offset in the source string corresponding to the
* next collation element. This value could be any of:
* <ul>
* <li>The index of the first character in the source string that matches
@@ -94,42 +97,33 @@ public final class CollationElementIterator {
* <li>The length of the source string, if iteration has reached the end.
* </li>
* </ul>
- *
- * @return The position of the collation element in the source string that
- * will be returned by the next invocation of the {@link #next()}
- * method.
*/
public int getOffset() {
return this.icuIterator.getOffset();
}
/**
- * Obtains the next collation element in the source string.
- *
- * @return the next collation element or {@code NULLORDER} if the end
- * of the iteration has been reached.
+ * Returns the next collation element in the source string or {@code NULLORDER} if
+ * the end of the iteration has been reached.
*/
public int next() {
return this.icuIterator.next();
}
/**
- * Obtains the previous collation element in the source string.
- *
- * @return the previous collation element, or {@code NULLORDER} when
- * the start of the iteration has been reached.
+ * Returns the previous collation element in the source string or {@code NULLORDER} if
+ * the start of the iteration has been reached.
*/
public int previous() {
return this.icuIterator.previous();
}
/**
- * Obtains the primary order of the specified collation element, i.e. the
+ * Returns the primary order of the specified collation element, i.e. the
* first 16 bits. This value is unsigned.
*
* @param order
* the element of the collation.
- * @return the element's 16 bit primary order.
*/
public static final int primaryOrder(int order) {
return CollationElementIteratorICU.primaryOrder(order);
@@ -149,12 +143,11 @@ public final class CollationElementIterator {
}
/**
- * Obtains the secondary order of the specified collation element, i.e. the
+ * Returns the secondary order of the specified collation element, i.e. the
* 16th to 23th bits, inclusive. This value is unsigned.
*
* @param order
* the element of the collator.
- * @return the 8 bit secondary order of the element.
*/
public static final short secondaryOrder(int order) {
return (short) CollationElementIteratorICU.secondaryOrder(order);
@@ -209,12 +202,11 @@ public final class CollationElementIterator {
}
/**
- * Obtains the tertiary order of the specified collation element, i.e. the
+ * Returns the tertiary order of the specified collation element, i.e. the
* last 8 bits. This value is unsigned.
*
* @param order
* the element of the collation.
- * @return the 8 bit tertiary order of the element.
*/
public static final short tertiaryOrder(int order) {
return (short) CollationElementIteratorICU.tertiaryOrder(order);
diff --git a/luni/src/main/java/java/text/DateFormat.java b/luni/src/main/java/java/text/DateFormat.java
index ac64eed..3055843 100644
--- a/luni/src/main/java/java/text/DateFormat.java
+++ b/luni/src/main/java/java/text/DateFormat.java
@@ -388,6 +388,9 @@ public abstract class DateFormat extends Format {
*/
public static final DateFormat getDateInstance(int style, Locale locale) {
checkDateStyle(style);
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
return new SimpleDateFormat(LocaleData.get(locale).getDateFormat(style), locale);
}
@@ -440,6 +443,9 @@ public abstract class DateFormat extends Format {
public static final DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) {
checkTimeStyle(timeStyle);
checkDateStyle(dateStyle);
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
LocaleData localeData = LocaleData.get(locale);
String pattern = localeData.getDateFormat(dateStyle) + " " + localeData.getTimeFormat(timeStyle);
return new SimpleDateFormat(pattern, locale);
@@ -457,6 +463,12 @@ public abstract class DateFormat extends Format {
}
/**
+ * @hide for internal use only.
+ */
+ public static final void set24HourTimePref(boolean is24Hour) {
+ }
+
+ /**
* Returns the {@code NumberFormat} used by this {@code DateFormat}.
*
* @return the {@code NumberFormat} used by this date format.
@@ -508,6 +520,10 @@ public abstract class DateFormat extends Format {
*/
public static final DateFormat getTimeInstance(int style, Locale locale) {
checkTimeStyle(style);
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
+
return new SimpleDateFormat(LocaleData.get(locale).getTimeFormat(style), locale);
}
diff --git a/luni/src/main/java/java/text/DateFormatSymbols.java b/luni/src/main/java/java/text/DateFormatSymbols.java
index e75b82c..ee34bbd 100644
--- a/luni/src/main/java/java/text/DateFormatSymbols.java
+++ b/luni/src/main/java/java/text/DateFormatSymbols.java
@@ -62,16 +62,14 @@ public class DateFormatSymbols implements Serializable, Cloneable {
transient LocaleData localeData;
// Localized display names.
- String[][] zoneStrings;
- // Has the user called setZoneStrings?
- transient boolean customZoneStrings;
+ private String[][] zoneStrings;
- /**
- * Locale, necessary to lazily load time zone strings. We force the time
- * zone names to load upon serialization, so this will never be needed
- * post deserialization.
+ /*
+ * Locale, necessary to lazily load time zone strings. Added to the serialized form for
+ * Android's L release. May be null if the object was deserialized using data from older
+ * releases. See b/16502916.
*/
- transient final Locale locale;
+ private final Locale locale;
/**
* Gets zone strings, initializing them if necessary. Does not create
@@ -102,7 +100,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
* the locale.
*/
public DateFormatSymbols(Locale locale) {
- this.locale = locale;
+ this.locale = LocaleData.mapInvalidAndNullLocales(locale);
this.localPatternChars = SimpleDateFormat.PATTERN_CHARS;
this.localeData = LocaleData.get(locale);
@@ -152,6 +150,12 @@ public class DateFormatSymbols implements Serializable, Cloneable {
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
+
+ Locale locale = this.locale;
+ if (locale == null) {
+ // Before the L release Android did not serialize the locale. Handle its absence.
+ locale = Locale.getDefault();
+ }
this.localeData = LocaleData.get(locale);
}
@@ -215,7 +219,6 @@ public class DateFormatSymbols implements Serializable, Cloneable {
// 'zoneStrings' is so large, we just print a representative value.
return getClass().getName() +
"[amPmStrings=" + Arrays.toString(ampms) +
- ",customZoneStrings=" + customZoneStrings +
",eras=" + Arrays.toString(eras) +
",localPatternChars=" + localPatternChars +
",months=" + Arrays.toString(months) +
@@ -251,8 +254,6 @@ public class DateFormatSymbols implements Serializable, Cloneable {
/**
* Returns the pattern characters used by {@link SimpleDateFormat} to
* specify date and time fields.
- *
- * @return a string containing the pattern characters.
*/
public String getLocalPatternChars() {
return localPatternChars;
@@ -485,6 +486,25 @@ public class DateFormatSymbols implements Serializable, Cloneable {
}
}
this.zoneStrings = clone2dStringArray(zoneStrings);
- this.customZoneStrings = true;
+ }
+
+ /**
+ * Returns the display name of the timezone specified. Returns null if no name was found in the
+ * zone strings.
+ *
+ * @param daylight whether to return the daylight savings or the standard name
+ * @param style one of the {@link TimeZone} styles such as {@link TimeZone#SHORT}
+ *
+ * @hide used internally
+ */
+ String getTimeZoneDisplayName(TimeZone tz, boolean daylight, int style) {
+ if (style != TimeZone.SHORT && style != TimeZone.LONG) {
+ throw new IllegalArgumentException("Bad style: " + style);
+ }
+
+ // If custom zone strings have been set with setZoneStrings() we use those. Otherwise we
+ // use the ones from LocaleData.
+ String[][] zoneStrings = internalZoneStrings();
+ return TimeZoneNames.getDisplayName(zoneStrings, tz.getID(), daylight, style);
}
}
diff --git a/luni/src/main/java/java/text/DecimalFormat.java b/luni/src/main/java/java/text/DecimalFormat.java
index 948bec1..554fd74 100644
--- a/luni/src/main/java/java/text/DecimalFormat.java
+++ b/luni/src/main/java/java/text/DecimalFormat.java
@@ -373,11 +373,11 @@ import libcore.icu.NativeDecimalFormat;
* digits is fixed at one and there is no exponent grouping.
* <li>Exponential patterns may not contain grouping separators.
* </ul>
- * <a name="sigdig">
+ * <a name="sigdig"></a>
* <h4> <strong><font color="red">NEW</font>&nbsp;</strong> Significant
* Digits</h4>
* <p>
- * </a> {@code DecimalFormat} has two ways of controlling how many digits are
+ * {@code DecimalFormat} has two ways of controlling how many digits are
* shown: (a) significant digit counts or (b) integer and fraction digit counts.
* Integer and fraction digit counts are described above. When a formatter uses
* significant digits counts, the number of integer and fraction digits is not
@@ -487,6 +487,12 @@ import libcore.icu.NativeDecimalFormat;
* <em>escapes</em> the following character. If there is no character after
* the pad escape, then the pattern is illegal.</li>
* </ul>
+ *
+ * <h4>Serialization</h4>
+ * <p>
+ * Features marked as <strong><font color="red">NEW</font></strong> and patterns that use
+ * characters not documented above are unlikely to serialize/deserialize correctly.
+ *
* <h4>Synchronization</h4>
* <p>
* {@code DecimalFormat} objects are not synchronized. Multiple threads should
@@ -574,6 +580,7 @@ public class DecimalFormat extends NumberFormat {
*/
public void applyLocalizedPattern(String pattern) {
ndf.applyLocalizedPattern(pattern);
+ updateFieldsFromNative();
}
/**
@@ -586,7 +593,18 @@ public class DecimalFormat extends NumberFormat {
* if the pattern cannot be parsed.
*/
public void applyPattern(String pattern) {
+ // The underlying ICU4C accepts a super-set of the pattern spec documented by the Android
+ // APIs. For example, rounding increments (pattern characters '1'-'9'). They will work but
+ // see class doc for issues with serialization/deserialization they may cause.
ndf.applyPattern(pattern);
+ updateFieldsFromNative();
+ }
+
+ private void updateFieldsFromNative() {
+ maximumIntegerDigits = ndf.getMaximumIntegerDigits();
+ minimumIntegerDigits = ndf.getMinimumIntegerDigits();
+ maximumFractionDigits = ndf.getMaximumFractionDigits();
+ minimumFractionDigits = ndf.getMinimumFractionDigits();
}
/**
@@ -659,21 +677,6 @@ public class DecimalFormat extends NumberFormat {
@Override
public StringBuffer format(double value, StringBuffer buffer, FieldPosition position) {
checkBufferAndFieldPosition(buffer, position);
- // All float/double/Float/Double formatting ends up here...
- if (roundingMode == RoundingMode.UNNECESSARY) {
- // ICU4C doesn't support this rounding mode, so we have to fake it.
- try {
- setRoundingMode(RoundingMode.UP);
- String upResult = format(value, new StringBuffer(), new FieldPosition(0)).toString();
- setRoundingMode(RoundingMode.DOWN);
- String downResult = format(value, new StringBuffer(), new FieldPosition(0)).toString();
- if (!upResult.equals(downResult)) {
- throw new ArithmeticException("rounding mode UNNECESSARY but rounding required");
- }
- } finally {
- setRoundingMode(RoundingMode.UNNECESSARY);
- }
- }
buffer.append(ndf.formatDouble(value, position));
return buffer;
}
@@ -735,16 +738,6 @@ public class DecimalFormat extends NumberFormat {
}
/**
- * Returns the multiplier which is applied to the number before formatting
- * or after parsing.
- *
- * @return the multiplier.
- */
- public int getMultiplier() {
- return ndf.getMultiplier();
- }
-
- /**
* Returns the prefix which is formatted or parsed before a negative number.
*
* @return the negative prefix.
@@ -826,16 +819,10 @@ public class DecimalFormat extends NumberFormat {
// In this implementation, NativeDecimalFormat is wrapped to
// fulfill most of the format and parse feature. And this method is
// delegated to the wrapped instance of NativeDecimalFormat.
+ super.setParseIntegerOnly(value);
ndf.setParseIntegerOnly(value);
}
- /**
- * Indicates whether parsing with this decimal format will only
- * return numbers of type {@code java.lang.Integer}.
- *
- * @return {@code true} if this {@code DecimalFormat}'s parse method only
- * returns {@code java.lang.Integer}; {@code false} otherwise.
- */
@Override
public boolean isParseIntegerOnly() {
return ndf.isParseIntegerOnly();
@@ -898,9 +885,6 @@ public class DecimalFormat extends NumberFormat {
/**
* Sets the {@code DecimalFormatSymbols} used by this decimal format.
- *
- * @param value
- * the {@code DecimalFormatSymbols} to set.
*/
public void setDecimalFormatSymbols(DecimalFormatSymbols value) {
if (value != null) {
@@ -913,24 +897,17 @@ public class DecimalFormat extends NumberFormat {
/**
* Sets the currency used by this decimal format. The min and max fraction
* digits remain the same.
- *
- * @param currency
- * the currency this {@code DecimalFormat} should use.
- * @see DecimalFormatSymbols#setCurrency(Currency)
*/
@Override
public void setCurrency(Currency currency) {
- ndf.setCurrency(Currency.getInstance(currency.getCurrencyCode()));
- symbols.setCurrency(currency);
+ Currency instance = Currency.getInstance(currency.getCurrencyCode());
+ symbols.setCurrency(instance);
+ ndf.setCurrency(symbols.getCurrencySymbol(), currency.getCurrencyCode());
}
/**
- * Sets whether the decimal separator is shown when there are no fractional
+ * Sets whether the decimal separator is shown even when there are no fractional
* digits.
- *
- * @param value
- * {@code true} if the decimal separator should always be
- * formatted; {@code false} otherwise.
*/
public void setDecimalSeparatorAlwaysShown(boolean value) {
ndf.setDecimalSeparatorAlwaysShown(value);
@@ -940,20 +917,14 @@ public class DecimalFormat extends NumberFormat {
* Sets the number of digits grouped together by the grouping separator.
* This only allows to set the primary grouping size; the secondary grouping
* size can only be set with a pattern.
- *
- * @param value
- * the number of digits grouped together.
*/
public void setGroupingSize(int value) {
ndf.setGroupingSize(value);
}
/**
- * Sets whether or not grouping will be used in this format. Grouping
- * affects both parsing and formatting.
- *
- * @param value
- * {@code true} if grouping is used; {@code false} otherwise.
+ * Sets whether or not digit grouping will be used in this format. Grouping
+ * affects both formatting and parsing.
*/
@Override
public void setGroupingUsed(boolean value) {
@@ -961,9 +932,8 @@ public class DecimalFormat extends NumberFormat {
}
/**
- * Indicates whether grouping will be used in this format.
- *
- * @return {@code true} if grouping is used; {@code false} otherwise.
+ * Returns true if digit grouping is used in this format. Grouping affects both
+ * formatting and parsing.
*/
@Override
public boolean isGroupingUsed() {
@@ -974,8 +944,6 @@ public class DecimalFormat extends NumberFormat {
* Sets the maximum number of digits after the decimal point.
* If the value passed is negative then it is replaced by 0.
* Regardless of this setting, no more than 340 digits will be used.
- *
- * @param value the maximum number of fraction digits.
*/
@Override
public void setMaximumFractionDigits(int value) {
@@ -989,8 +957,6 @@ public class DecimalFormat extends NumberFormat {
* Sets the maximum number of digits before the decimal point.
* If the value passed is negative then it is replaced by 0.
* Regardless of this setting, no more than 309 digits will be used.
- *
- * @param value the maximum number of integer digits.
*/
@Override
public void setMaximumIntegerDigits(int value) {
@@ -1002,8 +968,6 @@ public class DecimalFormat extends NumberFormat {
* Sets the minimum number of digits after the decimal point.
* If the value passed is negative then it is replaced by 0.
* Regardless of this setting, no more than 340 digits will be used.
- *
- * @param value the minimum number of fraction digits.
*/
@Override
public void setMinimumFractionDigits(int value) {
@@ -1015,8 +979,6 @@ public class DecimalFormat extends NumberFormat {
* Sets the minimum number of digits before the decimal point.
* If the value passed is negative then it is replaced by 0.
* Regardless of this setting, no more than 309 digits will be used.
- *
- * @param value the minimum number of integer digits.
*/
@Override
public void setMinimumIntegerDigits(int value) {
@@ -1025,11 +987,20 @@ public class DecimalFormat extends NumberFormat {
}
/**
+ * Returns the multiplier which is applied to the number before formatting
+ * or after parsing. The multiplier is meant for tasks like parsing percentages.
+ * For example, given a multiplier of 100, 1.23 would be formatted as "123" and
+ * "123" would be parsed as 1.23.
+ */
+ public int getMultiplier() {
+ return ndf.getMultiplier();
+ }
+
+ /**
* Sets the multiplier which is applied to the number before formatting or
- * after parsing.
- *
- * @param value
- * the multiplier.
+ * after parsing. The multiplier meant for tasks like parsing percentages.
+ * For example, given a multiplier of 100, 1.23 would be formatted as "123" and
+ * "123" would be parsed as 1.23.
*/
public void setMultiplier(int value) {
ndf.setMultiplier(value);
@@ -1037,9 +1008,6 @@ public class DecimalFormat extends NumberFormat {
/**
* Sets the prefix which is formatted or parsed before a negative number.
- *
- * @param value
- * the negative prefix.
*/
public void setNegativePrefix(String value) {
ndf.setNegativePrefix(value);
@@ -1047,9 +1015,6 @@ public class DecimalFormat extends NumberFormat {
/**
* Sets the suffix which is formatted or parsed after a negative number.
- *
- * @param value
- * the negative suffix.
*/
public void setNegativeSuffix(String value) {
ndf.setNegativeSuffix(value);
@@ -1057,9 +1022,6 @@ public class DecimalFormat extends NumberFormat {
/**
* Sets the prefix which is formatted or parsed before a positive number.
- *
- * @param value
- * the positive prefix.
*/
public void setPositivePrefix(String value) {
ndf.setPositivePrefix(value);
@@ -1067,9 +1029,6 @@ public class DecimalFormat extends NumberFormat {
/**
* Sets the suffix which is formatted or parsed after a positive number.
- *
- * @param value
- * the positive suffix.
*/
public void setPositiveSuffix(String value) {
ndf.setPositiveSuffix(value);
@@ -1239,9 +1198,12 @@ public class DecimalFormat extends NumberFormat {
throw new NullPointerException("roundingMode == null");
}
this.roundingMode = roundingMode;
- if (roundingMode != RoundingMode.UNNECESSARY) { // ICU4C doesn't support UNNECESSARY.
- double roundingIncrement = 1.0 / Math.pow(10, Math.max(0, getMaximumFractionDigits()));
- ndf.setRoundingMode(roundingMode, roundingIncrement);
- }
+ // DecimalFormat does not allow specification of a rounding increment. If anything other
+ // than 0.0 is used here the resulting DecimalFormat cannot be deserialized because the
+ // serialization format does not include rounding increment information.
+ double roundingIncrement = 0.0;
+ ndf.setRoundingMode(roundingMode, roundingIncrement);
}
+
+ public String toString() { return ndf.toString(); }
}
diff --git a/luni/src/main/java/java/text/DecimalFormatSymbols.java b/luni/src/main/java/java/text/DecimalFormatSymbols.java
index 708b291..fba2d6e 100644
--- a/luni/src/main/java/java/text/DecimalFormatSymbols.java
+++ b/luni/src/main/java/java/text/DecimalFormatSymbols.java
@@ -50,7 +50,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
private char percent;
private char perMill;
private char monetarySeparator;
- private char minusSign;
+ private String minusSign;
private String infinity, NaN, currencySymbol, intlCurrencySymbol;
private transient Currency currency;
@@ -81,6 +81,11 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
* the locale.
*/
public DecimalFormatSymbols(Locale locale) {
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
+
+ locale = LocaleData.mapInvalidAndNullLocales(locale);
LocaleData localeData = LocaleData.get(locale);
this.zeroDigit = localeData.zeroDigit;
this.digit = '#';
@@ -179,7 +184,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
groupingSeparator == obj.groupingSeparator &&
infinity.equals(obj.infinity) &&
intlCurrencySymbol.equals(obj.intlCurrencySymbol) &&
- minusSign == obj.minusSign &&
+ minusSign.equals(obj.minusSign) &&
monetarySeparator == obj.monetarySeparator &&
NaN.equals(obj.NaN) &&
patternSeparator == obj.patternSeparator &&
@@ -288,6 +293,16 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
* @return the minus sign as a character.
*/
public char getMinusSign() {
+ if (minusSign.length() == 1) {
+ return minusSign.charAt(0);
+ }
+
+ throw new UnsupportedOperationException(
+ "Minus sign spans multiple characters: " + minusSign);
+ }
+
+ /** @hide */
+ public String getMinusSignString() {
return minusSign;
}
@@ -366,7 +381,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
result = 31*result + percent;
result = 31*result + perMill;
result = 31*result + monetarySeparator;
- result = 31*result + minusSign;
+ result = 31*result + minusSign.hashCode();
result = 31*result + exponentSeparator.hashCode();
result = 31*result + infinity.hashCode();
result = 31*result + NaN.hashCode();
@@ -487,7 +502,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
* the minus sign character.
*/
public void setMinusSign(char value) {
- this.minusSign = value;
+ this.minusSign = String.valueOf(value);
}
/**
diff --git a/luni/src/main/java/java/text/FieldPosition.java b/luni/src/main/java/java/text/FieldPosition.java
index d5bccc7..c26ba33 100644
--- a/luni/src/main/java/java/text/FieldPosition.java
+++ b/luni/src/main/java/java/text/FieldPosition.java
@@ -21,152 +21,114 @@ package java.text;
* Identifies fields in formatted strings. If a {@code FieldPosition} is passed
* to the format method with such a parameter, then the indices will be set to
* the start and end indices of the field in the formatted string.
- * <p>
- * A {@code FieldPosition} can be created by using the integer constants in the
+ *
+ * <p>A {@code FieldPosition} can be created by using the integer constants in the
* various format classes (for example {@code NumberFormat.INTEGER_FIELD}) or
* one of the fields of type {@code Format.Field}.
- * <p>
- * If more than one field information is needed, the method
+ *
+ * <p>If more than one field position is needed, the method
* {@link NumberFormat#formatToCharacterIterator(Object)} should be used.
*/
public class FieldPosition {
- private int myField, beginIndex, endIndex;
-
- private Format.Field myAttribute;
-
- /**
- * Constructs a new {@code FieldPosition} for the specified field.
- *
- * @param field
- * the field to identify.
- */
- public FieldPosition(int field) {
- myField = field;
- }
-
- /**
- * Constructs a new {@code FieldPosition} for the specified {@code Field}
- * attribute.
- *
- * @param attribute
- * the field attribute to identify.
- */
- public FieldPosition(Format.Field attribute) {
- myAttribute = attribute;
- myField = -1;
- }
-
- /**
- * Constructs a new {@code FieldPosition} for the specified {@code Field}
- * attribute and field id.
- *
- * @param attribute
- * the field attribute to identify.
- * @param field
- * the field to identify.
- */
- public FieldPosition(Format.Field attribute, int field) {
- myAttribute = attribute;
- myField = field;
- }
-
- void clear() {
- beginIndex = endIndex = 0;
- }
-
- /**
- * Compares the specified object to this field position and indicates if
- * they are equal. In order to be equal, {@code object} must be an instance
- * of {@code FieldPosition} with the same field, begin index and end index.
- *
- * @param object
- * the object to compare with this object.
- * @return {@code true} if the specified object is equal to this field
- * position; {@code false} otherwise.
- * @see #hashCode
- */
- @Override
- public boolean equals(Object object) {
- if (!(object instanceof FieldPosition)) {
- return false;
- }
- FieldPosition pos = (FieldPosition) object;
- return myField == pos.myField && myAttribute == pos.myAttribute
- && beginIndex == pos.beginIndex && endIndex == pos.endIndex;
- }
-
- /**
- * Returns the index of the beginning of the field.
- *
- * @return the first index of the field.
- */
- public int getBeginIndex() {
- return beginIndex;
- }
-
- /**
- * Returns the index one past the end of the field.
- *
- * @return one past the index of the last character in the field.
- */
- public int getEndIndex() {
- return endIndex;
- }
-
- /**
- * Returns the field which is being identified.
- *
- * @return the field constant.
- */
- public int getField() {
- return myField;
- }
-
- /**
- * Returns the attribute which is being identified.
- *
- * @return the field.
- */
- public Format.Field getFieldAttribute() {
- return myAttribute;
- }
-
- @Override
- public int hashCode() {
- int attributeHash = (myAttribute == null) ? 0 : myAttribute.hashCode();
- return attributeHash + myField * 10 + beginIndex * 100 + endIndex;
- }
-
- /**
- * Sets the index of the beginning of the field.
- *
- * @param index
- * the index of the first character in the field.
- */
- public void setBeginIndex(int index) {
- beginIndex = index;
- }
-
- /**
- * Sets the index of the end of the field.
- *
- * @param index
- * one past the index of the last character in the field.
- */
- public void setEndIndex(int index) {
- endIndex = index;
- }
-
- /**
- * Returns the string representation of this field position.
- *
- * @return the string representation of this field position.
- */
- @Override
- public String toString() {
- return getClass().getName() + "[attribute=" + myAttribute + ", field="
- + myField + ", beginIndex=" + beginIndex + ", endIndex="
- + endIndex + "]";
+ private int field;
+ private int beginIndex;
+ private int endIndex;
+ private Format.Field attribute;
+
+ /**
+ * Constructs a new {@code FieldPosition} for the given field id.
+ */
+ public FieldPosition(int field) {
+ this.field = field;
+ }
+
+ /**
+ * Constructs a new {@code FieldPosition} for the given {@code Field} attribute.
+ */
+ public FieldPosition(Format.Field attribute) {
+ this.attribute = attribute;
+ this.field = -1;
+ }
+
+ /**
+ * Constructs a new {@code FieldPosition} for the given {@code Field} attribute and field id.
+ */
+ public FieldPosition(Format.Field attribute, int field) {
+ this.attribute = attribute;
+ this.field = field;
+ }
+
+ /**
+ * Compares the given object to this field position and indicates if
+ * they are equal. In order to be equal, {@code object} must be an instance
+ * of {@code FieldPosition} with the same field, begin index and end index.
+ */
+ @Override public boolean equals(Object object) {
+ if (!(object instanceof FieldPosition)) {
+ return false;
}
+ FieldPosition pos = (FieldPosition) object;
+ return field == pos.field && this.attribute == pos.attribute &&
+ beginIndex == pos.beginIndex && endIndex == pos.endIndex;
+ }
+
+ /**
+ * Returns the index of the beginning of the field.
+ */
+ public int getBeginIndex() {
+ return beginIndex;
+ }
+
+ /**
+ * Returns the index one past the end of the field.
+ */
+ public int getEndIndex() {
+ return endIndex;
+ }
+
+ /**
+ * Returns the field which is being identified.
+ */
+ public int getField() {
+ return field;
+ }
+
+ /**
+ * Returns the attribute which is being identified.
+ */
+ public Format.Field getFieldAttribute() {
+ return attribute;
+ }
+
+ @Override public int hashCode() {
+ int attributeHash = (attribute == null) ? 0 : attribute.hashCode();
+ return attributeHash + field * 10 + beginIndex * 100 + endIndex;
+ }
+
+ /**
+ * Sets the index of the beginning of the field.
+ */
+ public void setBeginIndex(int index) {
+ beginIndex = index;
+ }
+
+ /**
+ * Sets the index of the end of the field.
+ */
+ public void setEndIndex(int index) {
+ endIndex = index;
+ }
+
+ /**
+ * Returns the string representation of this field position.
+ */
+ @Override public String toString() {
+ return getClass().getName() + "[" +
+ "attribute=" + attribute +
+ ",field=" + field +
+ ",beginIndex=" + beginIndex +
+ ",endIndex=" + endIndex +
+ "]";
+ }
}
diff --git a/luni/src/main/java/java/text/MessageFormat.java b/luni/src/main/java/java/text/MessageFormat.java
index 2ab78db..cf306a7 100644
--- a/luni/src/main/java/java/text/MessageFormat.java
+++ b/luni/src/main/java/java/text/MessageFormat.java
@@ -45,7 +45,7 @@ import libcore.util.EmptyArray;
* behavior. Any locale-specific behavior is defined by the pattern that you
* provide as well as the subformats used for inserted arguments.
*
- * <h4><a name="patterns">Patterns and their interpretation</a></h4>
+ * <h4><a name="patterns"></a>Patterns and their interpretation</h4>
*
* {@code MessageFormat} uses patterns of the following form:
* <blockquote>
@@ -321,7 +321,7 @@ import libcore.util.EmptyArray;
* // result now equals {new String("z")}
* </pre>
* </blockquote>
- * <h4><a name="synchronization">Synchronization</a></h4>
+ * <h4><a name="synchronization"></a>Synchronization</h4>
* <p>
* Message formats are not synchronized. It is recommended to create separate
* format instances for each thread. If multiple threads access a format
diff --git a/luni/src/main/java/java/text/NumberFormat.java b/luni/src/main/java/java/text/NumberFormat.java
index 36fdd0f..a1f10d4 100644
--- a/luni/src/main/java/java/text/NumberFormat.java
+++ b/luni/src/main/java/java/text/NumberFormat.java
@@ -155,8 +155,10 @@ public abstract class NumberFormat extends Format {
private boolean groupingUsed = true, parseIntegerOnly = false;
- private int maximumIntegerDigits = 40, minimumIntegerDigits = 1,
- maximumFractionDigits = 3, minimumFractionDigits = 0;
+ int maximumIntegerDigits = 40;
+ int minimumIntegerDigits = 1;
+ int maximumFractionDigits = 3;
+ int minimumFractionDigits = 0;
/**
* Used by subclasses. This was public in Java 5.
@@ -208,8 +210,7 @@ public abstract class NumberFormat extends Format {
* @return the formatted string.
*/
public final String format(double value) {
- return format(value, new StringBuffer(), new FieldPosition(0))
- .toString();
+ return format(value, new StringBuffer(), new FieldPosition(0)).toString();
}
/**
@@ -241,8 +242,7 @@ public abstract class NumberFormat extends Format {
* @return the formatted string.
*/
public final String format(long value) {
- return format(value, new StringBuffer(), new FieldPosition(0))
- .toString();
+ return format(value, new StringBuffer(), new FieldPosition(0)).toString();
}
/**
@@ -301,7 +301,11 @@ public abstract class NumberFormat extends Format {
double dv = ((Number) object).doubleValue();
return format(dv, buffer, field);
}
- throw new IllegalArgumentException("Bad class: " + object.getClass());
+ if (object == null) {
+ throw new IllegalArgumentException("Can't format null object");
+ } else {
+ throw new IllegalArgumentException("Bad class: " + object.getClass());
+ }
}
/**
@@ -349,6 +353,10 @@ public abstract class NumberFormat extends Format {
* @return a {@code NumberFormat} for handling currency values.
*/
public static NumberFormat getCurrencyInstance(Locale locale) {
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
+
return getInstance(LocaleData.get(locale).currencyPattern, locale);
}
@@ -372,6 +380,10 @@ public abstract class NumberFormat extends Format {
* @return a {@code NumberFormat} for handling integers.
*/
public static NumberFormat getIntegerInstance(Locale locale) {
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
+
NumberFormat result = getInstance(LocaleData.get(locale).integerPattern, locale);
result.setParseIntegerOnly(true);
return result;
@@ -465,6 +477,9 @@ public abstract class NumberFormat extends Format {
* @return a {@code NumberFormat} for handling {@code Number} objects.
*/
public static NumberFormat getNumberInstance(Locale locale) {
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
return getInstance(LocaleData.get(locale).numberPattern, locale);
}
@@ -492,6 +507,10 @@ public abstract class NumberFormat extends Format {
* treated as 5,300%, which is rarely what you intended.
*/
public static NumberFormat getPercentInstance(Locale locale) {
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
+
return getInstance(LocaleData.get(locale).percentPattern, locale);
}
@@ -514,11 +533,8 @@ public abstract class NumberFormat extends Format {
}
/**
- * Indicates whether this number format only parses integer numbers. Parsing
+ * Returns true if this number format only parses integer numbers. Parsing
* stops if a decimal separator is encountered.
- *
- * @return {@code true} if this number format only parses integers,
- * {@code false} if if parsese integers as well as fractions.
*/
public boolean isParseIntegerOnly() {
return parseIntegerOnly;
@@ -742,10 +758,6 @@ public abstract class NumberFormat extends Format {
* The instances of this inner class are used as attribute keys and values
* in {@code AttributedCharacterIterator} that the
* {@link NumberFormat#formatToCharacterIterator(Object)} method returns.
- * <p>
- * There is no public constructor in this class, the only instances are the
- * constants defined here.
- * <p>
*/
public static class Field extends Format.Field {
@@ -809,9 +821,6 @@ public abstract class NumberFormat extends Format {
/**
* Constructs a new instance of {@code NumberFormat.Field} with the
* given field name.
- *
- * @param fieldName
- * the field name.
*/
protected Field(String fieldName) {
super(fieldName);
diff --git a/luni/src/main/java/java/text/RuleBasedCollator.java b/luni/src/main/java/java/text/RuleBasedCollator.java
index cda06db..4e84ef7 100644
--- a/luni/src/main/java/java/text/RuleBasedCollator.java
+++ b/luni/src/main/java/java/text/RuleBasedCollator.java
@@ -20,243 +20,60 @@ package java.text;
import libcore.icu.RuleBasedCollatorICU;
/**
- * A concrete implementation class for {@code Collation}.
- * <p>
- * {@code RuleBasedCollator} has the following restrictions for efficiency
- * (other subclasses may be used for more complex languages):
- * <ol>
- * <li> If a French secondary ordering is specified it applies to the whole
- * collator object.</li>
- * <li> All non-mentioned Unicode characters are at the end of the collation
- * order.</li>
- * <li> If a character is not located in the {@code RuleBasedCollator}, the
- * default Unicode Collation Algorithm (UCA) rule-based table is automatically
- * searched as a backup.</li>
- * </ol>
- * <p>
- * The collation table is composed of a list of collation rules, where each rule
- * is of three forms:
- * <blockquote>
- * <pre>
- * &lt;modifier&gt;
- * &lt;relation&gt; &lt;text-argument&gt;
- * &lt;reset&gt; &lt;text-argument&gt;
- * </pre>
- * </blockquote>
- * <p>
- * The rule elements are defined as follows:
- * <ul type="disc">
- * <li><strong>Modifier</strong>: There is a single modifier which is used to
- * specify that all accents (secondary differences) are backwards:
- * <ul type=square>
- * <li>'@' : Indicates that accents are sorted backwards, as in French.
- * </ul>
- * </li>
- * <li><strong>Relation</strong>: The relations are the following:
- * <ul type=square>
- * <li>'&lt;' : Greater, as a letter difference (primary)
- * <li>';' : Greater, as an accent difference (secondary)
- * <li>',' : Greater, as a case difference (tertiary)
- * <li>'=' : Equal
- * </ul>
- * </li>
- * <li><strong>Text-Argument</strong>: A text-argument is any sequence of
- * characters, excluding special characters (that is, common whitespace
- * characters [0009-000D, 0020] and rule syntax characters [0021-002F,
- * 003A-0040, 005B-0060, 007B-007E]). If those characters are desired, you can
- * put them in single quotes (for example, use '&amp;' for ampersand). Note that
- * unquoted white space characters are ignored; for example, {@code b c} is
- * treated as {@code bc}.</li>
- * <li><strong>Reset</strong>: There is a single reset which is used primarily
- * for contractions and expansions, but which can also be used to add a
- * modification at the end of a set of rules:
- * <ul type=square>
- * <li>'&amp;' : Indicates that the next rule follows the position to where the reset
- * text-argument would be sorted.
- * </ul>
- * </li>
- * </ul>
- * <p>
- * This sounds more complicated than it is in practice. For example, the
- * following are equivalent ways of expressing the same thing:
- * <blockquote>
+ * A concrete subclass of {@link Collator}.
+ * It is based on the ICU RuleBasedCollator which implements the
+ * CLDR and Unicode collation algorithms.
+ *
+ * <p>Most of the time, you create a {@link Collator} instance for a {@link java.util.Locale}
+ * by calling the {@link Collator#getInstance} factory method.
+ * You can construct a {@code RuleBasedCollator} if you need a custom sort order.
+ *
+ * <p>The root collator's sort order is the CLDR root collation order
+ * which in turn is the Unicode default sort order with a few modifications.
+ * A {@code RuleBasedCollator} is built from a rule {@code String} which changes the
+ * sort order of some characters and strings relative to the default order.
+ *
+ * <p>A rule string usually contains one or more rule chains.
+ * A rule chain consists of a reset followed by one or more rules.
+ * The reset anchors the following rules in the default sort order.
+ * The rules change the order of the their characters and strings
+ * relative to the reset point.
+ *
+ * <p>A reset is an ampersand {@code &amp;} followed by one or more characters for the reset position.
+ * A rule is a relation operator, which specifies the level of difference,
+ * also followed by one or more characters.
+ * A multi-character rule creates a "contraction".
+ * A multi-character reset position usually creates "expansions".
+ *
+ * <p>For example, the following rules
+ * make "ä" sort with a diacritic-like (secondary) difference from "ae"
+ * (like in German phonebook sorting),
+ * and make "Ã¥" and "aa" sort as a base letter (primary) after "z" (like in Danish).
+ * Uppercase forms sort with a case-like (tertiary) difference after their lowercase forms.
*
- * <pre>
- * a < b < c
- * a < b & b < c
- * a < c & a < b
- * </pre>
- *
- * </blockquote>
- * <p>
- * Notice that the order is important, as the subsequent item goes immediately
- * after the text-argument. The following are not equivalent:
* <blockquote>
- *
* <pre>
- * a < b & a < c
- * a < c & a < b
+ * &amp;AE&lt;&lt;ä &lt;&lt;&lt;Ä
+ * &amp;z&lt;Ã¥&lt;&lt;&lt;Ã…&lt;&lt;&lt;aa&lt;&lt;&lt;Aa&lt;&lt;&lt;AA
* </pre>
- *
* </blockquote>
- * <p>
- * Either the text-argument must already be present in the sequence, or some
- * initial substring of the text-argument must be present. For example
- * {@code "a < b & ae < e"} is valid since "a" is present in the sequence before
- * "ae" is reset. In this latter case, "ae" is not entered and treated as a
- * single character; instead, "e" is sorted as if it were expanded to two
- * characters: "a" followed by an "e". This difference appears in natural
- * languages: in traditional Spanish "ch" is treated as if it contracts to a
- * single character (expressed as {@code "c < ch < d"}), while in traditional
- * German a-umlaut is treated as if it expands to two characters (expressed as
- * {@code "a,A < b,B ... & ae;\u00e3 & AE;\u00c3"}, where \u00e3 and \u00c3
- * are the escape sequences for a-umlaut).
- * <h4>Ignorable Characters</h4>
- * <p>
- * For ignorable characters, the first rule must start with a relation (the
- * examples we have used above are really fragments; {@code "a < b"} really
- * should be {@code "< a < b"}). If, however, the first relation is not
- * {@code "<"}, then all text-arguments up to the first {@code "<"} are
- * ignorable. For example, {@code ", - < a < b"} makes {@code "-"} an ignorable
- * character.
- * <h4>Normalization and Accents</h4>
- * <p>
- * {@code RuleBasedCollator} automatically processes its rule table to include
- * both pre-composed and combining-character versions of accented characters.
- * Even if the provided rule string contains only base characters and separate
- * combining accent characters, the pre-composed accented characters matching
- * all canonical combinations of characters from the rule string will be entered
- * in the table.
- * <p>
- * This allows you to use a RuleBasedCollator to compare accented strings even
- * when the collator is set to NO_DECOMPOSITION. However, if the strings to be
- * collated contain combining sequences that may not be in canonical order, you
- * should set the collator to CANONICAL_DECOMPOSITION to enable sorting of
- * combining sequences. For more information, see <a
- * href="http://www.aw.com/devpress">The Unicode Standard, Version 3.0</a>.
- * <h4>Errors</h4>
- * <p>
- * The following rules are not valid:
- * <ul type="disc">
- * <li>A text-argument contains unquoted punctuation symbols, for example
- * {@code "a < b-c < d"}.</li>
- * <li>A relation or reset character is not followed by a text-argument, for
- * example {@code "a < , b"}.</li>
- * <li>A reset where the text-argument (or an initial substring of the
- * text-argument) is not already in the sequence or allocated in the default UCA
- * table, for example {@code "a < b & e < f"}.</li>
- * </ul>
- * <p>
- * If you produce one of these errors, {@code RuleBasedCollator} throws a
- * {@code ParseException}.
- * <h4>Examples</h4>
- * <p>
- * Normally, to create a rule-based collator object, you will use
- * {@code Collator}'s factory method {@code getInstance}. However, to create a
- * rule-based collator object with specialized rules tailored to your needs, you
- * construct the {@code RuleBasedCollator} with the rules contained in a
- * {@code String} object. For example:
- * <blockquote>
- *
- * <pre>
- * String Simple = "< a < b < c < d";
- * RuleBasedCollator mySimple = new RuleBasedCollator(Simple);
- * </pre>
- *
- * </blockquote>
- * <p>
- * Or:
- * <blockquote>
- *
- * <pre>
- * String Norwegian = "< a,A< b,B< c,C< d,D< e,E< f,F< g,G< h,H< i,I"
- * + "< j,J< k,K< l,L< m,M< n,N< o,O< p,P< q,Q< r,R"
- * + "< s,S< t,T< u,U< v,V< w,W< x,X< y,Y< z,Z"
- * + "< \u00E5=a\u030A,\u00C5=A\u030A"
- * + ";aa,AA< \u00E6,\u00C6< \u00F8,\u00D8";
- * RuleBasedCollator myNorwegian = new RuleBasedCollator(Norwegian);
- * </pre>
- *
- * </blockquote>
- * <p>
- * Combining {@code Collator}s is as simple as concatenating strings. Here is
- * an example that combines two {@code Collator}s from two different locales:
- * <blockquote>
- *
- * <pre>
- * // Create an en_US Collator object
- * RuleBasedCollator en_USCollator = (RuleBasedCollator)Collator
- * .getInstance(new Locale("en", "US", ""));
- *
- * // Create a da_DK Collator object
- * RuleBasedCollator da_DKCollator = (RuleBasedCollator)Collator
- * .getInstance(new Locale("da", "DK", ""));
*
- * // Combine the two collators
- * // First, get the collation rules from en_USCollator
- * String en_USRules = en_USCollator.getRules();
- *
- * // Second, get the collation rules from da_DKCollator
- * String da_DKRules = da_DKCollator.getRules();
- *
- * RuleBasedCollator newCollator = new RuleBasedCollator(en_USRules + da_DKRules);
- * // newCollator has the combined rules
- * </pre>
- *
- * </blockquote>
- * <p>
- * The next example shows to make changes on an existing table to create a new
- * {@code Collator} object. For example, add {@code "& C < ch, cH, Ch, CH"} to
- * the {@code en_USCollator} object to create your own:
- * <blockquote>
- *
- * <pre>
- * // Create a new Collator object with additional rules
- * String addRules = "& C < ch, cH, Ch, CH";
- *
- * RuleBasedCollator myCollator = new RuleBasedCollator(en_USCollator + addRules);
- * // myCollator contains the new rules
- * </pre>
- *
- * </blockquote>
- * <p>
- * The following example demonstrates how to change the order of non-spacing
- * accents:
- * <blockquote>
- *
- * <pre>
- * // old rule
- * String oldRules = "= \u00a8 ; \u00af ; \u00bf" + "< a , A ; ae, AE ; \u00e6 , \u00c6"
- * + "< b , B < c, C < e, E & C < d, D";
- *
- * // change the order of accent characters
- * String addOn = "& \u00bf ; \u00af ; \u00a8;";
- *
- * RuleBasedCollator myCollator = new RuleBasedCollator(oldRules + addOn);
- * </pre>
- *
- * </blockquote>
- * <p>
- * The last example shows how to put new primary ordering in before the default
- * setting. For example, in the Japanese {@code Collator}, you can either sort
- * English characters before or after Japanese characters:
- * <blockquote>
- *
- * <pre>
- * // get en_US Collator rules
- * RuleBasedCollator en_USCollator = (RuleBasedCollator)
- * Collator.getInstance(Locale.US);
- *
- * // add a few Japanese character to sort before English characters
- * // suppose the last character before the first base letter 'a' in
- * // the English collation rule is \u30A2
- * String jaString = "& \u30A2 , \u30FC < \u30C8";
+ * <p>For details see
+ * <ul>
+ * <li>CLDR <a href="http://www.unicode.org/reports/tr35/tr35-collation.html#Rules">Collation Rule Syntax</a>
+ * <li>ICU User Guide <a href="http://userguide.icu-project.org/collation/customization">Collation Customization</a>
+ * </ul>
*
- * RuleBasedCollator myJapaneseCollator =
- * new RuleBasedCollator(en_USCollator.getRules() + jaString);
- * </pre>
+ * <p>Note: earlier versions of {@code RuleBasedCollator} up to and including Android 4.4 (KitKat)
+ * allowed the omission of the reset from the first rule chain.
+ * This was interpreted as an implied reset after the last non-Han script in the default order.
+ * However, this is not a useful reset position, except for large tailorings of
+ * Han characters themselves.
+ * Starting with the CLDR 24 collation specification and the ICU 53 implementation,
+ * the initial reset is required.
*
- * </blockquote>
+ * <p>If the rule string does not follow the syntax, then {@code RuleBasedCollator} throws a
+ * {@code ParseException}.
*/
public class RuleBasedCollator extends Collator {
RuleBasedCollator(RuleBasedCollatorICU wrapper) {
@@ -265,13 +82,11 @@ public class RuleBasedCollator extends Collator {
/**
* Constructs a new instance of {@code RuleBasedCollator} using the
- * specified {@code rules}. The {@code rules} are usually either
- * hand-written based on the {@link RuleBasedCollator class description} or
- * the result of a former {@link #getRules()} call.
+ * specified {@code rules}. (See the {@link RuleBasedCollator class description}.)
* <p>
- * Note that the {@code rules} are actually interpreted as a delta to the
- * standard Unicode Collation Algorithm (UCA). This differs
- * slightly from other implementations which work with full {@code rules}
+ * Note that the {@code rules} are interpreted as a delta to the
+ * default sort order. This differs
+ * from other implementations which work with full {@code rules}
* specifications and may result in different behavior.
*
* @param rules
@@ -286,9 +101,6 @@ public class RuleBasedCollator extends Collator {
if (rules == null) {
throw new NullPointerException("rules == null");
}
- if (rules.isEmpty()) {
- throw new ParseException("empty rules", 0);
- }
try {
icuColl = new RuleBasedCollatorICU(rules);
} catch (Exception e) {
@@ -336,14 +148,9 @@ public class RuleBasedCollator extends Collator {
/**
* Returns the collation rules of this collator. These {@code rules} can be
* fed into the {@code RuleBasedCollator(String)} constructor.
- * <p>
- * Note that the {@code rules} are actually interpreted as a delta to the
- * standard Unicode Collation Algorithm (UCA). Hence, an empty {@code rules}
- * string results in the default UCA rules being applied. This differs
- * slightly from other implementations which work with full {@code rules}
- * specifications and may result in different behavior.
*
- * @return the collation rules.
+ * <p>The returned string will be empty unless you constructed the instance yourself.
+ * The string forms of the collation rules are omitted to save space on the device.
*/
public String getRules() {
return icuColl.getRules();
@@ -367,13 +174,6 @@ public class RuleBasedCollator extends Collator {
* the collation rules, strength and decomposition mode for this
* {@code RuleBasedCollator}. See the {@code Collator} class description
* for an example of use.
- * <p>
- * General recommendation: If comparisons are to be done with the same strings
- * multiple times, it is more efficient to generate {@code CollationKey}
- * objects for the strings and use
- * {@code CollationKey.compareTo(CollationKey)} for the comparisons. If each
- * string is compared to only once, using
- * {@code RuleBasedCollator.compare(String, String)} has better performance.
*
* @param source
* the source text.
diff --git a/luni/src/main/java/java/text/SimpleDateFormat.java b/luni/src/main/java/java/text/SimpleDateFormat.java
index 5fd8a56..8f83ff7 100644
--- a/luni/src/main/java/java/text/SimpleDateFormat.java
+++ b/luni/src/main/java/java/text/SimpleDateFormat.java
@@ -517,7 +517,8 @@ public class SimpleDateFormat extends DateFormat {
int next, last = -1, count = 0;
calendar.setTime(date);
if (field != null) {
- field.clear();
+ field.setBeginIndex(0);
+ field.setEndIndex(0);
}
final int patternLength = pattern.length();
@@ -623,8 +624,7 @@ public class SimpleDateFormat extends DateFormat {
break;
case MILLISECOND_FIELD:
dateFormatField = Field.MILLISECOND;
- int value = calendar.get(Calendar.MILLISECOND);
- appendNumber(buffer, count, value);
+ appendMilliseconds(buffer, count, calendar.get(Calendar.MILLISECOND));
break;
case STAND_ALONE_DAY_OF_WEEK_FIELD:
dateFormatField = Field.DAY_OF_WEEK;
@@ -740,15 +740,9 @@ public class SimpleDateFormat extends DateFormat {
TimeZone tz = calendar.getTimeZone();
boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);
int style = count < 4 ? TimeZone.SHORT : TimeZone.LONG;
- if (!formatData.customZoneStrings) {
- buffer.append(tz.getDisplayName(daylight, style, formatData.locale));
- return;
- }
- // We can't call TimeZone.getDisplayName() because it would not use
- // the custom DateFormatSymbols of this SimpleDateFormat.
- String custom = TimeZoneNames.getDisplayName(formatData.zoneStrings, tz.getID(), daylight, style);
- if (custom != null) {
- buffer.append(custom);
+ String zoneString = formatData.getTimeZoneDisplayName(tz, daylight, style);
+ if (zoneString != null) {
+ buffer.append(zoneString);
return;
}
}
@@ -759,21 +753,29 @@ public class SimpleDateFormat extends DateFormat {
// See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts.
// @param generalTimeZone "GMT-08:00" rather than "-0800".
private void appendNumericTimeZone(StringBuffer buffer, int count, boolean generalTimeZone) {
- int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
- char sign = '+';
- if (offset < 0) {
- sign = '-';
- offset = -offset;
- }
- if (generalTimeZone || count == 4) {
- buffer.append("GMT");
+ int offsetMillis = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
+ boolean includeGmt = generalTimeZone || count == 4;
+ boolean includeMinuteSeparator = generalTimeZone || count >= 4;
+ buffer.append(TimeZone.createGmtOffsetString(includeGmt, includeMinuteSeparator,
+ offsetMillis));
+ }
+
+ private void appendMilliseconds(StringBuffer buffer, int count, int value) {
+ // Unlike other fields, milliseconds are truncated by count. So 361 formatted SS is "36".
+ numberFormat.setMinimumIntegerDigits((count > 3) ? 3 : count);
+ numberFormat.setMaximumIntegerDigits(10);
+ // We need to left-justify.
+ if (count == 1) {
+ value /= 100;
+ } else if (count == 2) {
+ value /= 10;
}
- buffer.append(sign);
- appendNumber(buffer, 2, offset / 3600000);
- if (generalTimeZone || count >= 4) {
- buffer.append(':');
+ FieldPosition p = new FieldPosition(0);
+ numberFormat.format(Integer.valueOf(value), buffer, p);
+ if (count > 3) {
+ numberFormat.setMinimumIntegerDigits(count - 3);
+ numberFormat.format(Integer.valueOf(0), buffer, p);
}
- appendNumber(buffer, 2, (offset % 3600000) / 60000);
}
private void appendNumber(StringBuffer buffer, int count, int value) {
@@ -905,8 +907,7 @@ public class SimpleDateFormat extends DateFormat {
field = Calendar.SECOND;
break;
case MILLISECOND_FIELD:
- field = Calendar.MILLISECOND;
- break;
+ return parseFractionalSeconds(string, offset, absolute);
case STAND_ALONE_DAY_OF_WEEK_FIELD:
return parseDayOfWeek(string, offset, true);
case DAY_OF_WEEK_FIELD:
@@ -951,6 +952,31 @@ public class SimpleDateFormat extends DateFormat {
return offset;
}
+ /**
+ * Parses the fractional seconds section of a formatted date and assigns
+ * it to the {@code Calendar.MILLISECOND} field. Note that fractional seconds
+ * are somewhat unique, because they are zero suffixed.
+ */
+ private int parseFractionalSeconds(String string, int offset, int count) {
+ final ParsePosition parsePosition = new ParsePosition(offset);
+ final Number fractionalSeconds = parseNumber(count, string, parsePosition);
+ if (fractionalSeconds == null) {
+ return -parsePosition.getErrorIndex() - 1;
+ }
+
+ // NOTE: We could've done this using two parses instead. The first parse
+ // looking at |count| digits (to verify the date matched the format), and
+ // then a second parse that consumed just the first three digits. That
+ // would've avoided the floating point arithmetic, but would've demanded
+ // that we round values ourselves.
+ final double result = fractionalSeconds.doubleValue();
+ final int numDigitsParsed = parsePosition.getIndex() - offset;
+ final double divisor = Math.pow(10, numDigitsParsed);
+
+ calendar.set(Calendar.MILLISECOND, (int) ((result / divisor) * 1000));
+ return parsePosition.getIndex();
+ }
+
private int parseDayOfWeek(String string, int offset, boolean standAlone) {
LocaleData ld = formatData.localeData;
int index = parseText(string, offset,
@@ -1085,25 +1111,7 @@ public class SimpleDateFormat extends DateFormat {
}
if (max == 0) {
position.setIndex(index);
- Number n = numberFormat.parse(string, position);
- // In RTL locales, NumberFormat might have parsed "2012-" in an ISO date as the
- // negative number -2012.
- // Ideally, we wouldn't have this broken API that exposes a NumberFormat and expects
- // us to use it. The next best thing would be a way to ask the NumberFormat to parse
- // positive numbers only, but icu4c supports negative (BCE) years. The best we can do
- // is try to recognize when icu4c has done this, and undo it.
- if (n != null && n.longValue() < 0) {
- if (numberFormat instanceof DecimalFormat) {
- DecimalFormat df = (DecimalFormat) numberFormat;
- char lastChar = string.charAt(position.getIndex() - 1);
- char minusSign = df.getDecimalFormatSymbols().getMinusSign();
- if (lastChar == minusSign) {
- n = Long.valueOf(-n.longValue()); // Make the value positive.
- position.setIndex(position.getIndex() - 1); // Spit out the negative sign.
- }
- }
- }
- return n;
+ return numberFormat.parse(string, position);
}
int result = 0;
@@ -1168,6 +1176,8 @@ public class SimpleDateFormat extends DateFormat {
if (foundGMT) {
offset += 3;
}
+
+ // Check for an offset, which may have been preceded by "GMT"
char sign;
if (offset < string.length() && ((sign = string.charAt(offset)) == '+' || sign == '-')) {
ParsePosition position = new ParsePosition(offset + 1);
@@ -1195,10 +1205,14 @@ public class SimpleDateFormat extends DateFormat {
calendar.setTimeZone(new SimpleTimeZone(raw, ""));
return position.getIndex();
}
+
+ // If there was "GMT" but no offset.
if (foundGMT) {
calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
return offset;
}
+
+ // Exhaustively look for the string in this DateFormat's localized time zone strings.
for (String[] row : formatData.internalZoneStrings()) {
for (int i = TimeZoneNames.LONG_NAME; i < TimeZoneNames.NAME_COUNT; ++i) {
if (row[i] == null) {
diff --git a/luni/src/main/java/java/text/spi/BreakIteratorProvider.java b/luni/src/main/java/java/text/spi/BreakIteratorProvider.java
deleted file mode 100644
index a28bc53..0000000
--- a/luni/src/main/java/java/text/spi/BreakIteratorProvider.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.text.spi;
-
-import java.text.BreakIterator;
-import java.util.Locale;
-import java.util.spi.LocaleServiceProvider;
-
-/**
- * This abstract class should be extended by service providers that provide
- * instances of {@code BreakIterator}.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class BreakIteratorProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected BreakIteratorProvider() {
- // Do nothing.
- }
-
- /**
- * Returns an instance of {@code BreakIterator} for word breaks in the
- * given locale.
- *
- * @param locale the locale
- * @return an instance of {@code BreakIterator}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract BreakIterator getWordInstance(Locale locale);
-
- /**
- * Returns an instance of {@code BreakIterator} for line breaks in the
- * given locale.
- *
- * @param locale the locale
- * @return an instance of {@code BreakIterator}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract BreakIterator getLineInstance(Locale locale);
-
- /**
- * Returns an instance of {@code BreakIterator} for character breaks in the
- * given locale.
- *
- * @param locale the locale
- * @return an instance of {@code BreakIterator}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract BreakIterator getCharacterInstance(Locale locale);
-
- /**
- * Returns an instance of {@code BreakIterator} for sentence breaks in the
- * given locale.
- *
- * @param locale the locale
- * @return an instance of {@code BreakIterator}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract BreakIterator getSentenceInstance(Locale locale);
-}
diff --git a/luni/src/main/java/java/text/spi/CollatorProvider.java b/luni/src/main/java/java/text/spi/CollatorProvider.java
deleted file mode 100644
index 0862432..0000000
--- a/luni/src/main/java/java/text/spi/CollatorProvider.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.text.spi;
-
-import java.text.Collator;
-import java.util.Locale;
-import java.util.spi.LocaleServiceProvider;
-
-/**
- * This abstract class should be extended by service providers which provide
- * instances of {@code Collator}.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class CollatorProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected CollatorProvider() {
- // Do nothing.
- }
-
- /**
- * Returns an instance of {@code Collator} for the given locale.
- *
- * @param locale the locale
- * @return an instance of {@code Collator}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract Collator getInstance(Locale locale);
-}
diff --git a/luni/src/main/java/java/text/spi/DateFormatProvider.java b/luni/src/main/java/java/text/spi/DateFormatProvider.java
deleted file mode 100644
index 59fefed..0000000
--- a/luni/src/main/java/java/text/spi/DateFormatProvider.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.text.spi;
-
-import java.text.DateFormat;
-import java.util.Locale;
-import java.util.spi.LocaleServiceProvider;
-
-/**
- * This abstract class should be extended by service providers that provide
- * instances of {@code DateFormat}.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class DateFormatProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected DateFormatProvider() {
- // Do nothing.
- }
-
- /**
- * Returns an instance of {@code DateFormat} that formats times
- * in the given style for the given locale.
- *
- * @param style the given time formatting style.
- * @param locale the locale
- * @return an instance of {@code DateFormat}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract DateFormat getTimeInstance(int style, Locale locale);
-
- /**
- * Returns an instance of {@code DateFormat} that formats dates
- * in the given style for the given locale.
- *
- * @param style the given date formatting style.
- * @param locale the locale
- * @return an instance of {@code DateFormat}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract DateFormat getDateInstance(int style, Locale locale);
-
- /**
- * Returns an instance of {@code DateFormat} that formats dates and times
- * in the given style for the given locale.
- *
- * @param dateStyle the given date formatting style.
- * @param timeStyle the given time formatting style.
- * @param locale the locale
- * @return an instance of {@code DateFormat}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale);
-}
diff --git a/luni/src/main/java/java/text/spi/DateFormatSymbolsProvider.java b/luni/src/main/java/java/text/spi/DateFormatSymbolsProvider.java
deleted file mode 100644
index cb34b71..0000000
--- a/luni/src/main/java/java/text/spi/DateFormatSymbolsProvider.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.text.spi;
-
-import java.text.DateFormatSymbols;
-import java.util.Locale;
-import java.util.spi.LocaleServiceProvider;
-
-/**
- * This abstract class should be extended by service providers that provide
- * instances of {@code DateFormatSymbols}.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class DateFormatSymbolsProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected DateFormatSymbolsProvider() {
- // Do nothing.
- }
-
- /**
- * Returns an instance of {@code DateFormatSymbols} for the given locale.
- *
- * @param locale the locale
- * @return an instance of {@code DateFormatSymbols}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract DateFormatSymbols getInstance(Locale locale);
-}
diff --git a/luni/src/main/java/java/text/spi/DecimalFormatSymbolsProvider.java b/luni/src/main/java/java/text/spi/DecimalFormatSymbolsProvider.java
deleted file mode 100644
index f9f5e7d..0000000
--- a/luni/src/main/java/java/text/spi/DecimalFormatSymbolsProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.text.spi;
-
-import java.text.DecimalFormatSymbols;
-import java.util.Locale;
-import java.util.spi.LocaleServiceProvider;
-
-/**
- * This abstract class should be extended by service providers that provide
- * instances of {@code DecimalFormatSymbols}.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class DecimalFormatSymbolsProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected DecimalFormatSymbolsProvider() {
- // Do nothing.
- }
-
- /**
- * Returns an instance of {@code DecimalFormatSymbols} for the given locale.
- *
- * @param locale the locale
- * @return an instance of {@code DecimalFormatSymbols}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract DecimalFormatSymbols getInstance(Locale locale);
-
-}
diff --git a/luni/src/main/java/java/text/spi/NumberFormatProvider.java b/luni/src/main/java/java/text/spi/NumberFormatProvider.java
deleted file mode 100644
index c889f78..0000000
--- a/luni/src/main/java/java/text/spi/NumberFormatProvider.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.text.spi;
-
-import java.text.NumberFormat;
-import java.util.Locale;
-import java.util.spi.LocaleServiceProvider;
-
-/**
- * This abstract class should be extended by service providers that provide
- * {@code NumberFormat} instances.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class NumberFormatProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected NumberFormatProvider() {
- // Do nothing.
- }
-
- /**
- * Returns an instance of {@code NumberFormat} that formats
- * monetary values for the given locale.
- *
- * @param locale the locale
- * @return an instance of {@code NumberFormat}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract NumberFormat getCurrencyInstance(Locale locale);
-
- /**
- * Returns an instance of {@code NumberFormat} that formats
- * integer values for the given locale. The returned {@code NumberFormat}
- * is configured to round floating point numbers to the nearest integer
- * using half-even rounding mode for formatting, and to parse only the
- * integer part of an input string.
- *
- * @param locale the locale
- * @return an instance of {@code NumberFormat}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract NumberFormat getIntegerInstance(Locale locale);
-
- /**
- * Returns an instance of {@code NumberFormat} class for general
- * use in the given locale.
- *
- * @param locale the locale
- * @return an instance of {@code NumberFormat}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract NumberFormat getNumberInstance(Locale locale);
-
- /**
- * Returns an instance of {@code NumberFormat} class that formats
- * percentage values for the given locale.
- *
- * @param locale the locale
- * @return an instance of {@code NumberFormat}
- * @throws NullPointerException if {@code locale == null}
- * @throws IllegalArgumentException
- * if locale isn't one of the locales returned from
- * getAvailableLocales().
- */
- public abstract NumberFormat getPercentInstance(Locale locale);
-}
diff --git a/luni/src/main/java/java/util/Arrays.java b/luni/src/main/java/java/util/Arrays.java
index d3e05a0..3b477d0 100644
--- a/luni/src/main/java/java/util/Arrays.java
+++ b/luni/src/main/java/java/util/Arrays.java
@@ -1319,31 +1319,25 @@ public class Arrays {
/*
* element is an array
*/
- if (!cl.isPrimitive()) {
+ if (element instanceof Object[]) {
return deepHashCode((Object[]) element);
- }
- if (cl.equals(int.class)) {
+ } else if (cl == int.class) {
return hashCode((int[]) element);
- }
- if (cl.equals(char.class)) {
+ } else if (cl == char.class) {
return hashCode((char[]) element);
- }
- if (cl.equals(boolean.class)) {
+ } else if (cl == boolean.class) {
return hashCode((boolean[]) element);
- }
- if (cl.equals(byte.class)) {
+ } else if (cl == byte.class) {
return hashCode((byte[]) element);
- }
- if (cl.equals(long.class)) {
+ } else if (cl == long.class) {
return hashCode((long[]) element);
- }
- if (cl.equals(float.class)) {
+ } else if (cl == float.class) {
return hashCode((float[]) element);
- }
- if (cl.equals(double.class)) {
+ } else if (cl == double.class) {
return hashCode((double[]) element);
+ } else {
+ return hashCode((short[]) element);
}
- return hashCode((short[]) element);
}
/**
@@ -1665,32 +1659,25 @@ public class Arrays {
/*
* compare as arrays
*/
- if (!cl1.isPrimitive()) {
+ if (e1 instanceof Object[]) {
return deepEquals((Object[]) e1, (Object[]) e2);
- }
-
- if (cl1.equals(int.class)) {
+ } else if (cl1 == int.class) {
return equals((int[]) e1, (int[]) e2);
- }
- if (cl1.equals(char.class)) {
+ } else if (cl1 == char.class) {
return equals((char[]) e1, (char[]) e2);
- }
- if (cl1.equals(boolean.class)) {
+ } else if (cl1 == boolean.class) {
return equals((boolean[]) e1, (boolean[]) e2);
- }
- if (cl1.equals(byte.class)) {
+ } else if (cl1 == byte.class) {
return equals((byte[]) e1, (byte[]) e2);
- }
- if (cl1.equals(long.class)) {
+ } else if (cl1 == long.class) {
return equals((long[]) e1, (long[]) e2);
- }
- if (cl1.equals(float.class)) {
+ } else if (cl1 == float.class) {
return equals((float[]) e1, (float[]) e2);
- }
- if (cl1.equals(double.class)) {
+ } else if (cl1 == double.class) {
return equals((double[]) e1, (double[]) e2);
+ } else {
+ return equals((short[]) e1, (short[]) e2);
}
- return equals((short[]) e1, (short[]) e2);
}
/**
diff --git a/luni/src/main/java/java/util/Calendar.java b/luni/src/main/java/java/util/Calendar.java
index 4ed2ad1..fc4cef6 100644
--- a/luni/src/main/java/java/util/Calendar.java
+++ b/luni/src/main/java/java/util/Calendar.java
@@ -642,13 +642,24 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
/**
* Field number for {@code get} and {@code set} indicating the
- * raw offset from GMT in milliseconds.
+ * raw (non-DST) offset from GMT in milliseconds.
+ * Equivalent to {@link java.util.TimeZone#getRawOffset}.
+ *
+ * <p>To determine the total offset from GMT at the time represented
+ * by this calendar, you will need to add the {@code ZONE_OFFSET} and
+ * {@code DST_OFFSET} fields.
*/
public static final int ZONE_OFFSET = 15;
/**
* Field number for {@code get} and {@code set} indicating the
- * daylight savings offset in milliseconds.
+ * daylight savings offset from the {@code ZONE_OFFSET} in milliseconds.
+ * Equivalent to {@link java.util.TimeZone#getDSTSavings} if the represented time
+ * falls inside DST, or 0 otherwise.
+ *
+ * <p>To determine the total offset from GMT at the time represented
+ * by this calendar, you will need to add the {@code ZONE_OFFSET} and
+ * {@code DST_OFFSET} fields.
*/
public static final int DST_OFFSET = 16;
@@ -716,6 +727,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
*/
protected Calendar(TimeZone timezone, Locale locale) {
this(timezone);
+ locale = LocaleData.mapInvalidAndNullLocales(locale);
LocaleData localeData = LocaleData.get(locale);
setFirstDayOfWeek(localeData.firstDayOfWeek.intValue());
setMinimalDaysInFirstWeek(localeData.minimalDaysInFirstWeek.intValue());
diff --git a/luni/src/main/java/java/util/Collections.java b/luni/src/main/java/java/util/Collections.java
index ce4e579..4541d64 100644
--- a/luni/src/main/java/java/util/Collections.java
+++ b/luni/src/main/java/java/util/Collections.java
@@ -1212,7 +1212,7 @@ public class Collections {
int length = c.size();
Object[] result = new Object[length];
Iterator<?> it = iterator();
- for (int i = length; --i >= 0;) {
+ for (int i = 0; i < length; i++) {
result[i] = it.next();
}
return result;
diff --git a/luni/src/main/java/java/util/Currency.java b/luni/src/main/java/java/util/Currency.java
index b5b04a0..f43ddae 100644
--- a/luni/src/main/java/java/util/Currency.java
+++ b/luni/src/main/java/java/util/Currency.java
@@ -35,7 +35,7 @@ public final class Currency implements Serializable {
private Currency(String currencyCode) {
this.currencyCode = currencyCode;
- String symbol = ICU.getCurrencySymbol(Locale.US.toString(), currencyCode);
+ String symbol = ICU.getCurrencySymbol(Locale.US, currencyCode);
if (symbol == null) {
throw new IllegalArgumentException("Unsupported ISO 4217 currency code: " +
currencyCode);
@@ -65,6 +65,9 @@ public final class Currency implements Serializable {
*/
public static Currency getInstance(Locale locale) {
synchronized (localesToCurrencies) {
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
Currency currency = localesToCurrencies.get(locale);
if (currency != null) {
return currency;
@@ -123,7 +126,7 @@ public final class Currency implements Serializable {
* @since 1.7
*/
public String getDisplayName(Locale locale) {
- return ICU.getCurrencyDisplayName(locale.toString(), currencyCode);
+ return ICU.getCurrencyDisplayName(locale, currencyCode);
}
/**
@@ -146,10 +149,9 @@ public final class Currency implements Serializable {
* <p>If there is no locale-specific currency symbol, the ISO 4217 currency code is returned.
*/
public String getSymbol(Locale locale) {
- if (locale.getCountry().length() == 0) {
- return currencyCode;
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
}
-
// Check the locale first, in case the locale has the same currency.
LocaleData localeData = LocaleData.get(locale);
if (localeData.internationalCurrencySymbol.equals(currencyCode)) {
@@ -157,7 +159,7 @@ public final class Currency implements Serializable {
}
// Try ICU, and fall back to the currency code if ICU has nothing.
- String symbol = ICU.getCurrencySymbol(locale.toString(), currencyCode);
+ String symbol = ICU.getCurrencySymbol(locale, currencyCode);
return symbol != null ? symbol : currencyCode;
}
diff --git a/luni/src/main/java/java/util/EnumMap.java b/luni/src/main/java/java/util/EnumMap.java
index 827fb51..dfacb46 100644
--- a/luni/src/main/java/java/util/EnumMap.java
+++ b/luni/src/main/java/java/util/EnumMap.java
@@ -174,7 +174,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements
@Override
@SuppressWarnings("unchecked")
public String toString() {
- if (-1 == prePosition) {
+ if (prePosition == -1) {
return super.toString();
}
return type.get(
@@ -183,7 +183,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements
}
private void checkStatus() {
- if (-1 == prePosition) {
+ if (prePosition == -1) {
throw new IllegalStateException();
}
}
diff --git a/luni/src/main/java/java/util/Formatter.java b/luni/src/main/java/java/util/Formatter.java
index 98bdccc..dd9a09a 100644
--- a/luni/src/main/java/java/util/Formatter.java
+++ b/luni/src/main/java/java/util/Formatter.java
@@ -2065,7 +2065,7 @@ public final class Formatter implements Closeable, Flushable {
formatToken.setPrecision(FormatToken.UNSET);
int startIndex = 0;
- if (result.charAt(0) == localeData.minusSign) {
+ if (startsWithMinusSign(result, localeData.minusSign)) {
if (formatToken.flagParenthesis) {
return wrapParentheses(result);
}
@@ -2081,8 +2081,9 @@ public final class Formatter implements Closeable, Flushable {
}
char firstChar = result.charAt(0);
- if (formatToken.flagZero && (firstChar == '+' || firstChar == localeData.minusSign)) {
- startIndex = 1;
+ if (formatToken.flagZero && (firstChar == '+' ||
+ startsWithMinusSign(result, localeData.minusSign))) {
+ startIndex = localeData.minusSign.length();
}
if (conversionType == 'a' || conversionType == 'A') {
@@ -2091,6 +2092,20 @@ public final class Formatter implements Closeable, Flushable {
return padding(result, startIndex);
}
+ private static boolean startsWithMinusSign(CharSequence cs, String minusSign) {
+ if (cs.length() < minusSign.length()) {
+ return false;
+ }
+
+ for (int i = 0; i < minusSign.length(); ++i) {
+ if (minusSign.charAt(i) != cs.charAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private void transformE(StringBuilder result) {
// All zeros in this method are *pattern* characters, so no localization.
final int precision = formatToken.getPrecision();
diff --git a/luni/src/main/java/java/util/GregorianCalendar.java b/luni/src/main/java/java/util/GregorianCalendar.java
index 9ff9ccc..be96684 100644
--- a/luni/src/main/java/java/util/GregorianCalendar.java
+++ b/luni/src/main/java/java/util/GregorianCalendar.java
@@ -331,7 +331,16 @@ public class GregorianCalendar extends Calendar {
setTimeInMillis(System.currentTimeMillis());
}
- GregorianCalendar(boolean ignored) {
+ /**
+ * A minimum-cost constructor that does not initialize the current time or perform any date
+ * calculations. For use internally when the time will be set later. Other constructors, such as
+ * {@link GregorianCalendar#GregorianCalendar()}, set the time to the current system clock
+ * and recalculate the fields incurring unnecessary cost when the time or fields will be set
+ * later.
+ *
+ * @hide used internally
+ */
+ public GregorianCalendar(boolean ignored) {
super(TimeZone.getDefault());
setFirstDayOfWeek(SUNDAY);
setMinimalDaysInFirstWeek(1);
@@ -458,16 +467,17 @@ public class GregorianCalendar extends Calendar {
complete();
}
- private void fullFieldsCalc(long timeVal, int zoneOffset) {
+ private void fullFieldsCalc() {
int millis = (int) (time % 86400000);
- long days = timeVal / 86400000;
+ long days = time / 86400000;
if (millis < 0) {
millis += 86400000;
days--;
}
- // Cannot add ZONE_OFFSET to time as it might overflow
- millis += zoneOffset;
+ // Adding fields[ZONE_OFFSET] to time might make it overflow, so we add
+ // it to millis (the number of milliseconds in the current day) instead.
+ millis += fields[ZONE_OFFSET];
while (millis < 0) {
millis += 86400000;
days--;
@@ -477,9 +487,9 @@ public class GregorianCalendar extends Calendar {
days++;
}
- int dayOfYear = computeYearAndDay(days, timeVal + zoneOffset);
+ int dayOfYear = computeYearAndDay(days, time + fields[ZONE_OFFSET]);
fields[DAY_OF_YEAR] = dayOfYear;
- if(fields[YEAR] == changeYear && gregorianCutover <= timeVal + zoneOffset){
+ if (fields[YEAR] == changeYear && gregorianCutover <= time + fields[ZONE_OFFSET]){
dayOfYear += currentYearSkew;
}
int month = dayOfYear / 32;
@@ -493,7 +503,7 @@ public class GregorianCalendar extends Calendar {
int dstOffset = fields[YEAR] <= 0 ? 0 : getTimeZone().getOffset(AD,
fields[YEAR], month, date, fields[DAY_OF_WEEK], millis);
if (fields[YEAR] > 0) {
- dstOffset -= zoneOffset;
+ dstOffset -= fields[ZONE_OFFSET];
}
fields[DST_OFFSET] = dstOffset;
if (dstOffset != 0) {
@@ -507,10 +517,10 @@ public class GregorianCalendar extends Calendar {
days++;
}
if (oldDays != days) {
- dayOfYear = computeYearAndDay(days, timeVal - zoneOffset
+ dayOfYear = computeYearAndDay(days, time - fields[ZONE_OFFSET]
+ dstOffset);
fields[DAY_OF_YEAR] = dayOfYear;
- if(fields[YEAR] == changeYear && gregorianCutover <= timeVal - zoneOffset + dstOffset){
+ if(fields[YEAR] == changeYear && gregorianCutover <= time - fields[ZONE_OFFSET] + dstOffset){
dayOfYear += currentYearSkew;
}
month = dayOfYear / 32;
@@ -567,10 +577,26 @@ public class GregorianCalendar extends Calendar {
TimeZone timeZone = getTimeZone();
int dstOffset = timeZone.inDaylightTime(new Date(time)) ? timeZone.getDSTSavings() : 0;
int zoneOffset = timeZone.getRawOffset();
+
+ // We unconditionally overwrite DST_OFFSET and ZONE_OFFSET with
+ // values from the timezone that's currently in use. This gives us
+ // much more consistent behavior, and matches ICU4J behavior (though
+ // it is inconsistent with the RI).
+ //
+ // Anything callers can do with ZONE_OFFSET they can do by constructing
+ // a SimpleTimeZone with the required offset.
+ //
+ // DST_OFFSET is a bit of a WTF, given that it's dependent on the rest
+ // of the fields. There's no sensible reason we'd want to allow it to
+ // be set, nor can we implement consistent full-fields calculation after
+ // this field is set without maintaining a large deal of additional state.
+ //
+ // At the very least, we will need isSet to differentiate between fields
+ // set by the user and fields set by our internal field calculation.
fields[DST_OFFSET] = dstOffset;
fields[ZONE_OFFSET] = zoneOffset;
- fullFieldsCalc(time, zoneOffset);
+ fullFieldsCalc();
for (int i = 0; i < FIELD_COUNT; i++) {
isSet[i] = true;
@@ -670,8 +696,16 @@ public class GregorianCalendar extends Calendar {
if (useMonth
&& (lastDateFieldSet == DAY_OF_WEEK || lastDateFieldSet == WEEK_OF_YEAR)) {
if (isSet[WEEK_OF_YEAR] && isSet[DAY_OF_WEEK]) {
- useMonth = lastDateFieldSet != WEEK_OF_YEAR && weekMonthSet
- && isSet[DAY_OF_WEEK];
+ if (lastDateFieldSet == WEEK_OF_YEAR) {
+ useMonth = false;
+ } else if (lastDateFieldSet == DAY_OF_WEEK) {
+ // DAY_OF_WEEK belongs to both the Month + Week + Day and the
+ // WeekOfYear + Day combinations. We're supposed to use the most
+ // recent combination, as specified by the single set field. We can't
+ // know for sure in this case, so we always prefer the week-month-day
+ // combination if week-month is already set.
+ useMonth = weekMonthSet;
+ }
} else if (isSet[DAY_OF_YEAR]) {
useMonth = isSet[DATE] && isSet[MONTH];
}
diff --git a/luni/src/main/java/java/util/HashMap.java b/luni/src/main/java/java/util/HashMap.java
index 80fbd0c..b6fe646 100644
--- a/luni/src/main/java/java/util/HashMap.java
+++ b/luni/src/main/java/java/util/HashMap.java
@@ -297,12 +297,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria
return e == null ? null : e.value;
}
- // Doug Lea's supplemental secondaryHash function (inlined).
- // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
- int hash = key.hashCode();
- hash ^= (hash >>> 20) ^ (hash >>> 12);
- hash ^= (hash >>> 7) ^ (hash >>> 4);
-
+ int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
@@ -327,12 +322,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria
return entryForNullKey != null;
}
- // Doug Lea's supplemental secondaryHash function (inlined).
- // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
- int hash = key.hashCode();
- hash ^= (hash >>> 20) ^ (hash >>> 12);
- hash ^= (hash >>> 7) ^ (hash >>> 4);
-
+ int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
@@ -344,15 +334,6 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria
return false;
}
- // Doug Lea's supplemental secondaryHash function (non-inlined).
- // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
- static int secondaryHash(Object key) {
- int hash = key.hashCode();
- hash ^= (hash >>> 20) ^ (hash >>> 12);
- hash ^= (hash >>> 7) ^ (hash >>> 4);
- return hash;
- }
-
/**
* Returns whether this map contains the specified value.
*
@@ -401,7 +382,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria
return putValueForNullKey(value);
}
- int hash = secondaryHash(key);
+ int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
@@ -464,7 +445,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria
return;
}
- int hash = secondaryHash(key);
+ int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
HashMapEntry<K, V> first = tab[index];
@@ -632,7 +613,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria
if (key == null) {
return removeNullKey();
}
- int hash = secondaryHash(key);
+ int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index], prev = null;
@@ -852,7 +833,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria
return e != null && Objects.equal(value, e.value);
}
- int hash = secondaryHash(key);
+ int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
@@ -880,7 +861,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria
return true;
}
- int hash = secondaryHash(key);
+ int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index], prev = null;
diff --git a/luni/src/main/java/java/util/IllformedLocaleException.java b/luni/src/main/java/java/util/IllformedLocaleException.java
new file mode 100644
index 0000000..3dec1cd
--- /dev/null
+++ b/luni/src/main/java/java/util/IllformedLocaleException.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.util;
+
+/**
+ * Thrown when a locale subtag or field is not well formed.
+ *
+ * See {@link Locale} and {@link Locale.Builder}.
+ *
+ * @since 1.7
+ */
+public class IllformedLocaleException extends RuntimeException {
+
+ private final int errorIndex;
+
+ /**
+ * Constructs a new instance with no detail message and an error index
+ * of {@code -1}.
+ */
+ public IllformedLocaleException() {
+ this(null, -1);
+ }
+
+ /**
+ * Constructs a new instance with the specified error message.
+ */
+ public IllformedLocaleException(String message) {
+ this(message, -1);
+ }
+
+ /**
+ * Constructs a new instance with the specified error message and
+ * error index.
+ */
+ public IllformedLocaleException(String message, int errorIndex) {
+ super(message);
+ this.errorIndex = errorIndex;
+ }
+
+ public int getErrorIndex() {
+ return errorIndex;
+ }
+}
diff --git a/luni/src/main/java/java/util/LinkedHashMap.java b/luni/src/main/java/java/util/LinkedHashMap.java
index e61b0f9..3d6e6c3 100644
--- a/luni/src/main/java/java/util/LinkedHashMap.java
+++ b/luni/src/main/java/java/util/LinkedHashMap.java
@@ -74,7 +74,7 @@ public class LinkedHashMap<K, V> extends HashMap<K, V> {
*
* @param initialCapacity
* the initial capacity of this map.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* when the capacity is less than zero.
*/
public LinkedHashMap(int initialCapacity) {
@@ -247,8 +247,7 @@ public class LinkedHashMap<K, V> extends HashMap<K, V> {
return e.value;
}
- // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
- int hash = secondaryHash(key);
+ int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java
index fc8f7c6..a6368e8 100644
--- a/luni/src/main/java/java/util/Locale.java
+++ b/luni/src/main/java/java/util/Locale.java
@@ -22,6 +22,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
import libcore.icu.ICU;
/**
@@ -40,7 +41,7 @@ import libcore.icu.ICU;
* rewriting happens even if you construct your own {@code Locale} object, not just for
* instances returned by the various lookup methods.
*
- * <a name="available_locales"><h3>Available locales</h3></a>
+ * <a name="available_locales"></a><h3>Available locales</h3>
* <p>This class' constructors do no error checking. You can create a {@code Locale} for languages
* and countries that don't exist, and you can create instances for combinations that don't
* exist (such as "de_US" for "German as spoken in the US").
@@ -59,7 +60,7 @@ import libcore.icu.ICU;
* device you're running on, or {@link Locale#getAvailableLocales} to get a list of all the locales
* available on the device you're running on.
*
- * <a name="locale_data"><h3>Locale data</h3></a>
+ * <a name="locale_data"></a><h3>Locale data</h3>
* <p>Note that locale data comes solely from ICU. User-supplied locale service providers (using
* the {@code java.text.spi} or {@code java.util.spi} mechanisms) are not supported.
*
@@ -79,24 +80,28 @@ import libcore.icu.ICU;
* <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-8">CLDR 1.8</a></td>
* <td><a href="http://www.unicode.org/versions/Unicode5.2.0/">Unicode 5.2</a></td></tr>
* <tr><td>Android 4.0 (Ice Cream Sandwich)</td>
- * <td>ICU 4.6</td>
+ * <td><a href="http://site.icu-project.org/download/46">ICU 4.6</a></td>
* <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-9">CLDR 1.9</a></td>
* <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr>
* <tr><td>Android 4.1 (Jelly Bean)</td>
- * <td>ICU 4.8</td>
+ * <td><a href="http://site.icu-project.org/download/48">ICU 4.8</a></td>
* <td><a href="http://cldr.unicode.org/index/downloads/cldr-2-0">CLDR 2.0</a></td>
* <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr>
* <tr><td>Android 4.3 (Jelly Bean MR2)</td>
- * <td>ICU 50</td>
+ * <td><a href="http://site.icu-project.org/download/50">ICU 50</a></td>
* <td><a href="http://cldr.unicode.org/index/downloads/cldr-22-1">CLDR 22.1</a></td>
* <td><a href="http://www.unicode.org/versions/Unicode6.2.0/">Unicode 6.2</a></td></tr>
* <tr><td>Android 4.4 (KitKat)</td>
- * <td>ICU 51</td>
+ * <td><a href="http://site.icu-project.org/download/51">ICU 51</a></td>
* <td><a href="http://cldr.unicode.org/index/downloads/cldr-23">CLDR 23</a></td>
* <td><a href="http://www.unicode.org/versions/Unicode6.2.0/">Unicode 6.2</a></td></tr>
+ * <tr><td>Android 5.0 (Lollipop)</td>
+ * <td><a href="http://site.icu-project.org/download/53">ICU 53</a></td>
+ * <td><a href="http://cldr.unicode.org/index/downloads/cldr-25">CLDR 25</a></td>
+ * <td><a href="http://www.unicode.org/versions/Unicode6.3.0/">Unicode 6.3</a></td></tr>
* </table>
*
- * <a name="default_locale"><h3>Be wary of the default locale</h3></a>
+ * <a name="default_locale"></a><h3>Be wary of the default locale</h3>
* <p>Note that there are many convenience methods that automatically use the default locale, but
* using them may lead to subtle bugs.
*
@@ -243,6 +248,31 @@ public final class Locale implements Cloneable, Serializable {
public static final Locale US = new Locale(true, "en", "US");
/**
+ * BCP-47 extension identifier (or "singleton") for the private
+ * use extension.
+ *
+ * See {@link #getExtension(char)} and {@link Builder#setExtension(char, String)}.
+ *
+ * @since 1.7
+ */
+ public static final char PRIVATE_USE_EXTENSION = 'x';
+
+ /**
+ * BCP-47 extension identifier (or "singleton") for the unicode locale extension.
+ *
+ *
+ * See {@link #getExtension(char)} and {@link Builder#setExtension(char, String)}.
+ *
+ * @since 1.7
+ */
+ public static final char UNICODE_LOCALE_EXTENSION = 'u';
+
+ /**
+ * ISO 639-3 generic code for undetermined languages.
+ */
+ private static final String UNDETERMINED_LANGUAGE = "und";
+
+ /**
* The current default locale. It is temporarily assigned to US because we
* need a default locale to lookup the real default locale.
*/
@@ -255,70 +285,663 @@ public final class Locale implements Cloneable, Serializable {
defaultLocale = new Locale(language, region, variant);
}
+ /**
+ * A class that helps construct {@link Locale} instances.
+ *
+ * Unlike the public {@code Locale} constructors, the methods of this class
+ * perform much stricter checks on their input.
+ *
+ * Validity checks on the {@code language}, {@code country}, {@code variant}
+ * and {@code extension} values are carried out as per the
+ * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> specification.
+ *
+ * In addition, we treat the <a href="http://www.unicode.org/reports/tr35/">
+ * Unicode locale extension</a> specially and provide methods to manipulate
+ * the structured state (keywords and attributes) specified therein.
+ *
+ * @since 1.7
+ */
+ public static final class Builder {
+ private String language;
+ private String region;
+ private String variant;
+ private String script;
+
+ private final Set<String> attributes;
+ private final Map<String, String> keywords;
+ private final Map<Character, String> extensions;
+
+ public Builder() {
+ language = region = variant = script = "";
+
+ // NOTE: We use sorted maps in the builder & the locale class itself
+ // because serialized forms of the unicode locale extension (and
+ // of the extension map itself) are specified to be in alphabetic
+ // order of keys.
+ attributes = new TreeSet<String>();
+ keywords = new TreeMap<String, String>();
+ extensions = new TreeMap<Character, String>();
+ }
+
+ /**
+ * Sets the locale language. If {@code language} is {@code null} or empty, the
+ * previous value is cleared.
+ *
+ * As per BCP-47, the language must be between 2 and 3 ASCII characters
+ * in length and must only contain characters in the range {@code [a-zA-Z]}.
+ *
+ * This value is usually an <a href="http://www.loc.gov/standards/iso639-2/">
+ * ISO-639-2</a> alpha-2 or alpha-3 code, though no explicit checks are
+ * carried out that it's a valid code in that namespace.
+ *
+ * Values are normalized to lower case.
+ *
+ * Note that we don't support BCP-47 "extlang" languages because they were
+ * only ever used to substitute for a lack of 3 letter language codes.
+ *
+ * @throws IllformedLocaleException if the language was invalid.
+ */
+ public Builder setLanguage(String language) {
+ this.language = normalizeAndValidateLanguage(language, true /* strict */);
+ return this;
+ }
+
+ private static String normalizeAndValidateLanguage(String language, boolean strict) {
+ if (language == null || language.isEmpty()) {
+ return "";
+ }
+
+ final String lowercaseLanguage = language.toLowerCase(Locale.ROOT);
+ if (!isValidBcp47Alpha(lowercaseLanguage, 2, 3)) {
+ if (strict) {
+ throw new IllformedLocaleException("Invalid language: " + language);
+ } else {
+ return UNDETERMINED_LANGUAGE;
+ }
+ }
+
+ return lowercaseLanguage;
+ }
+
+ /**
+ * Set the state of this builder to the parsed contents of the BCP-47 language
+ * tag {@code languageTag}.
+ *
+ * This method is equivalent to a call to {@link #clear} if {@code languageTag}
+ * is {@code null} or empty.
+ *
+ * <b>NOTE:</b> In contrast to {@link Locale#forLanguageTag(String)}, which
+ * simply ignores malformed input, this method will throw an exception if
+ * its input is malformed.
+ *
+ * @throws IllformedLocaleException if {@code languageTag} is not a well formed
+ * BCP-47 tag.
+ */
+ public Builder setLanguageTag(String languageTag) {
+ if (languageTag == null || languageTag.isEmpty()) {
+ clear();
+ return this;
+ }
+
+ final Locale fromIcu = forLanguageTag(languageTag, true /* strict */);
+ // When we ask ICU for strict parsing, it might return a null locale
+ // if the language tag is malformed.
+ if (fromIcu == null) {
+ throw new IllformedLocaleException("Invalid languageTag: " + languageTag);
+ }
+
+ setLocale(fromIcu);
+ return this;
+ }
+
+ /**
+ * Sets the locale region. If {@code region} is {@code null} or empty, the
+ * previous value is cleared.
+ *
+ * As per BCP-47, the region must either be a 2 character ISO-3166-1 code
+ * (each character in the range [a-zA-Z]) OR a 3 digit UN M.49 code.
+ *
+ * Values are normalized to upper case.
+ *
+ * @throws IllformedLocaleException if {@code} region is invalid.
+ */
+ public Builder setRegion(String region) {
+ this.region = normalizeAndValidateRegion(region, true /* strict */);
+ return this;
+ }
+
+ private static String normalizeAndValidateRegion(String region, boolean strict) {
+ if (region == null || region.isEmpty()) {
+ return "";
+ }
+
+ final String uppercaseRegion = region.toUpperCase(Locale.ROOT);
+ if (!isValidBcp47Alpha(uppercaseRegion, 2, 2) &&
+ !isUnM49AreaCode(uppercaseRegion)) {
+ if (strict) {
+ throw new IllformedLocaleException("Invalid region: " + region);
+ } else {
+ return "";
+ }
+ }
+
+ return uppercaseRegion;
+ }
+
+ /**
+ * Sets the locale variant. If {@code variant} is {@code null} or empty,
+ * the previous value is cleared.
+ *
+ * The input string my consist of one or more variants separated by
+ * valid separators ('-' or '_').
+ *
+ * As per BCP-47, each variant must be between 5 and 8 alphanumeric characters
+ * in length (each character in the range {@code [a-zA-Z0-9]}) but
+ * can be exactly 4 characters in length if the first character is a digit.
+ *
+ * Note that this is a much stricter interpretation of {@code variant}
+ * than the public {@code Locale} constructors. The latter allowed free form
+ * variants.
+ *
+ * Variants are case sensitive and all separators are normalized to {@code '_'}.
+ *
+ * @throws IllformedLocaleException if {@code} variant is invalid.
+ */
+ public Builder setVariant(String variant) {
+ this.variant = normalizeAndValidateVariant(variant);
+ return this;
+ }
+
+ private static String normalizeAndValidateVariant(String variant) {
+ if (variant == null || variant.isEmpty()) {
+ return "";
+ }
+
+ // Note that unlike extensions, we canonicalize to lower case alphabets
+ // and underscores instead of hyphens.
+ final String normalizedVariant = variant.replace('-', '_');
+ String[] subTags = normalizedVariant.split("_");
+
+ for (String subTag : subTags) {
+ if (!isValidVariantSubtag(subTag)) {
+ throw new IllformedLocaleException("Invalid variant: " + variant);
+ }
+ }
+
+ return normalizedVariant;
+ }
+
+ private static boolean isValidVariantSubtag(String subTag) {
+ // The BCP-47 spec states that :
+ // - Subtags can be between [5, 8] alphanumeric chars in length.
+ // - Subtags that start with a number are allowed to be 4 chars in length.
+ if (subTag.length() >= 5 && subTag.length() <= 8) {
+ if (isAsciiAlphaNum(subTag)) {
+ return true;
+ }
+ } else if (subTag.length() == 4) {
+ final char firstChar = subTag.charAt(0);
+ if ((firstChar >= '0' && firstChar <= '9') && isAsciiAlphaNum(subTag)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets the locale script. If {@code script} is {@code null} or empty,
+ * the previous value is cleared.
+ *
+ * As per BCP-47, the script must be 4 characters in length, and
+ * each character in the range {@code [a-zA-Z]}.
+ *
+ * A script usually represents a valid ISO 15924 script code, though no
+ * other registry or validity checks are performed.
+ *
+ * Scripts are normalized to title cased values.
+ *
+ * @throws IllformedLocaleException if {@code script} is invalid.
+ */
+ public Builder setScript(String script) {
+ this.script = normalizeAndValidateScript(script, true /* strict */);
+ return this;
+ }
+
+ private static String normalizeAndValidateScript(String script, boolean strict) {
+ if (script == null || script.isEmpty()) {
+ return "";
+ }
+
+ if (!isValidBcp47Alpha(script, 4, 4)) {
+ if (strict) {
+ throw new IllformedLocaleException("Invalid script: " + script);
+ } else {
+ return "";
+ }
+ }
+
+ return titleCaseAsciiWord(script);
+ }
+
+ /**
+ * Sets the state of the builder to the {@link Locale} represented by
+ * {@code locale}.
+ *
+ * Note that the locale's language, region and variant are validated as per
+ * the rules specified in {@link #setLanguage}, {@link #setRegion} and
+ * {@link #setVariant}.
+ *
+ * All existing builder state is discarded.
+ *
+ * @throws IllformedLocaleException if {@code locale} is invalid.
+ * @throws NullPointerException if {@code locale} is null.
+ */
+ public Builder setLocale(Locale locale) {
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
+
+ // Make copies of the existing values so that we don't partially
+ // update the state if we encounter an error.
+ final String backupLanguage = language;
+ final String backupRegion = region;
+ final String backupVariant = variant;
+
+ try {
+ setLanguage(locale.getLanguage());
+ setRegion(locale.getCountry());
+ setVariant(locale.getVariant());
+ } catch (IllformedLocaleException ifle) {
+ language = backupLanguage;
+ region = backupRegion;
+ variant = backupVariant;
+
+ throw ifle;
+ }
+
+ // The following values can be set only via the builder class, so
+ // there's no need to normalize them or check their validity.
+
+ this.script = locale.getScript();
+
+ extensions.clear();
+ extensions.putAll(locale.extensions);
+
+ keywords.clear();
+ keywords.putAll(locale.unicodeKeywords);
+
+ attributes.clear();
+ attributes.addAll(locale.unicodeAttributes);
+
+ return this;
+ }
+
+ /**
+ * Adds the specified attribute to the list of attributes in the unicode
+ * locale extension.
+ *
+ * Attributes must be between 3 and 8 characters in length, and each character
+ * must be in the range {@code [a-zA-Z0-9]}.
+ *
+ * Attributes are normalized to lower case values. All added attributes and
+ * keywords are combined to form a complete unicode locale extension on
+ * {@link Locale} objects built by this builder, and accessible via
+ * {@link Locale#getExtension(char)} with the {@link Locale#UNICODE_LOCALE_EXTENSION}
+ * key.
+ *
+ * @throws IllformedLocaleException if {@code attribute} is invalid.
+ * @throws NullPointerException if {@code attribute} is null.
+ */
+ public Builder addUnicodeLocaleAttribute(String attribute) {
+ if (attribute == null) {
+ throw new NullPointerException("attribute == null");
+ }
+
+ final String lowercaseAttribute = attribute.toLowerCase(Locale.ROOT);
+ if (!isValidBcp47Alphanum(lowercaseAttribute, 3, 8)) {
+ throw new IllformedLocaleException("Invalid locale attribute: " + attribute);
+ }
+
+ attributes.add(lowercaseAttribute);
+
+ return this;
+ }
+
+ /**
+ * Removes an attribute from the list of attributes in the unicode locale
+ * extension.
+ *
+ * {@code attribute} must be valid as per the rules specified in
+ * {@link #addUnicodeLocaleAttribute}.
+ *
+ * This method has no effect if {@code attribute} hasn't already been
+ * added.
+ *
+ * @throws IllformedLocaleException if {@code attribute} is invalid.
+ * @throws NullPointerException if {@code attribute} is null.
+ */
+ public Builder removeUnicodeLocaleAttribute(String attribute) {
+ if (attribute == null) {
+ throw new NullPointerException("attribute == null");
+ }
+
+ // Weirdly, remove is specified to check whether the attribute
+ // is valid, so we have to perform the full alphanumeric check here.
+ final String lowercaseAttribute = attribute.toLowerCase(Locale.ROOT);
+ if (!isValidBcp47Alphanum(lowercaseAttribute, 3, 8)) {
+ throw new IllformedLocaleException("Invalid locale attribute: " + attribute);
+ }
+
+ attributes.remove(attribute);
+ return this;
+ }
+
+ /**
+ * Sets the extension identified by {@code key} to {@code value}.
+ *
+ * {@code key} must be in the range {@code [a-zA-Z0-9]}.
+ *
+ * If {@code value} is {@code null} or empty, the extension is removed.
+ *
+ * In the general case, {@code value} must be a series of subtags separated
+ * by ({@code "-"} or {@code "_"}). Each subtag must be between
+ * 2 and 8 characters in length, and each character in the subtag must be in
+ * the range {@code [a-zA-Z0-9]}.
+ *
+ * <p>
+ * There are two special cases :
+ * <li>
+ * <ul>
+ * The unicode locale extension
+ * ({@code key == 'u'}, {@link Locale#UNICODE_LOCALE_EXTENSION}) : Setting
+ * the unicode locale extension results in all existing keyword and attribute
+ * state being replaced by the parsed result of {@code value}. For example,
+ * {@code builder.setExtension('u', "baaaz-baaar-fo-baar-ba-baaz")}
+ * is equivalent to:
+ * <pre>
+ * builder.addUnicodeLocaleAttribute("baaaz");
+ * builder.addUnicodeLocaleAttribute("baaar");
+ * builder.setUnicodeLocaleKeyword("fo", "baar");
+ * builder.setUnicodeLocaleKeyword("ba", "baaa");
+ * </pre>
+ * </ul>
+ * <ul>
+ * The private use extension
+ * ({@code key == 'x'}, {@link Locale#PRIVATE_USE_EXTENSION}) : Each subtag in a
+ * private use extension can be between 1 and 8 characters in length (in contrast
+ * to a minimum length of 2 for all other extensions).
+ * </ul>
+ * </li>
+ *
+ * @throws IllformedLocaleException if {@code value} is invalid.
+ */
+ public Builder setExtension(char key, String value) {
+ if (value == null || value.isEmpty()) {
+ extensions.remove(key);
+ return this;
+ }
+
+ final String normalizedValue = value.toLowerCase(Locale.ROOT).replace('_', '-');
+ final String[] subtags = normalizedValue.split("-");
+
+ // Lengths for subtags in the private use extension should be [1, 8] chars.
+ // For all other extensions, they should be [2, 8] chars.
+ //
+ // http://www.rfc-editor.org/rfc/bcp/bcp47.txt
+ final int minimumLength = (key == PRIVATE_USE_EXTENSION) ? 1 : 2;
+ for (String subtag : subtags) {
+ if (!isValidBcp47Alphanum(subtag, minimumLength, 8)) {
+ throw new IllformedLocaleException(
+ "Invalid private use extension : " + value);
+ }
+ }
+
+ // We need to take special action in the case of unicode extensions,
+ // since we claim to understand their keywords and attributes.
+ if (key == UNICODE_LOCALE_EXTENSION) {
+ // First clear existing attributes and keywords.
+ extensions.clear();
+ attributes.clear();
+
+ parseUnicodeExtension(subtags, keywords, attributes);
+ } else {
+ extensions.put(key, normalizedValue);
+ }
+
+ return this;
+ }
+
+ /**
+ * Clears all extensions from this builder. Note that this also implicitly
+ * clears all state related to the unicode locale extension; all attributes
+ * and keywords set by {@link #addUnicodeLocaleAttribute} and
+ * {@link #setUnicodeLocaleKeyword} are cleared.
+ */
+ public Builder clearExtensions() {
+ extensions.clear();
+ attributes.clear();
+ keywords.clear();
+ return this;
+ }
+
+ /**
+ * Adds a key / type pair to the list of unicode locale extension keys.
+ *
+ * {@code key} must be 2 characters in length, and each character must be
+ * in the range {@code [a-zA-Z0-9]}.
+ *
+ * {#code type} can either be empty, or a series of one or more subtags
+ * separated by a separator ({@code "-"} or {@code "_"}). Each subtag must
+ * be between 3 and 8 characters in length and each character in the subtag
+ * must be in the range {@code [a-zA-Z0-9]}.
+ *
+ * Note that the type is normalized to lower case, and all separators
+ * are normalized to {@code "-"}. All added attributes and
+ * keywords are combined to form a complete unicode locale extension on
+ * {@link Locale} objects built by this builder, and accessible via
+ * {@link Locale#getExtension(char)} with the {@link Locale#UNICODE_LOCALE_EXTENSION}
+ * key.
+ *
+ * @throws IllformedLocaleException if {@code key} or {@code value} are
+ * invalid.
+ */
+ public Builder setUnicodeLocaleKeyword(String key, String type) {
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+
+ if (type == null && keywords != null) {
+ keywords.remove(key);
+ return this;
+ }
+
+ final String lowerCaseKey = key.toLowerCase(Locale.ROOT);
+ // The key must be exactly two alphanumeric characters.
+ if (lowerCaseKey.length() != 2 || !isAsciiAlphaNum(lowerCaseKey)) {
+ throw new IllformedLocaleException("Invalid unicode locale keyword: " + key);
+ }
+
+ // The type can be one or more alphanumeric strings of length [3, 8] characters,
+ // separated by a separator char, which is one of "_" or "-". Though the spec
+ // doesn't require it, we normalize all "_" to "-" to make the rest of our
+ // processing easier.
+ final String lowerCaseType = type.toLowerCase(Locale.ROOT).replace("_", "-");
+ if (!isValidTypeList(lowerCaseType)) {
+ throw new IllformedLocaleException("Invalid unicode locale type: " + type);
+ }
+
+ // Everything checks out fine, add the <key, type> mapping to the list.
+ keywords.put(lowerCaseKey, lowerCaseType);
+
+ return this;
+ }
+
+ /**
+ * Clears all existing state from this builder.
+ */
+ public Builder clear() {
+ clearExtensions();
+ language = region = variant = script = "";
+
+ return this;
+ }
+
+ /**
+ * Constructs a locale from the existing state of the builder. Note that this
+ * method is guaranteed to succeed since field validity checks are performed
+ * at the point of setting them.
+ */
+ public Locale build() {
+ // NOTE: We need to make a copy of attributes, keywords and extensions
+ // because the RI allows this builder to reused.
+ return new Locale(language, region, variant, script,
+ attributes, keywords, extensions,
+ true /* has validated fields */);
+ }
+ }
+
+ /**
+ * Returns a locale for a given BCP-47 language tag. This method is more
+ * lenient than {@link Builder#setLanguageTag}. For a given language tag, parsing
+ * will proceed up to the first malformed subtag. All subsequent tags are discarded.
+ * Note that language tags use {@code -} rather than {@code _}, for example {@code en-US}.
+ *
+ * @throws NullPointerException if {@code languageTag} is {@code null}.
+ *
+ * @since 1.7
+ */
+ public static Locale forLanguageTag(String languageTag) {
+ if (languageTag == null) {
+ throw new NullPointerException("languageTag == null");
+ }
+
+ return forLanguageTag(languageTag, false /* strict */);
+ }
+
private transient String countryCode;
private transient String languageCode;
private transient String variantCode;
+ private transient String scriptCode;
+
+ /* Sorted, Unmodifiable */
+ private transient Set<String> unicodeAttributes;
+ /* Sorted, Unmodifiable */
+ private transient Map<String, String> unicodeKeywords;
+ /* Sorted, Unmodifiable */
+ private transient Map<Character, String> extensions;
+
+ /**
+ * Whether this instance was constructed from a builder. We can make
+ * stronger assumptions about the validity of Locale fields if this was
+ * constructed by a builder.
+ */
+ private transient final boolean hasValidatedFields;
+
private transient String cachedToStringResult;
+ private transient String cachedLanguageTag;
+ private transient String cachedIcuLocaleId;
/**
* There's a circular dependency between toLowerCase/toUpperCase and
* Locale.US. Work around this by avoiding these methods when constructing
* the built-in locales.
- *
- * @param unused required for this constructor to have a unique signature
*/
- private Locale(boolean unused, String lowerCaseLanguageCode, String upperCaseCountryCode) {
+ private Locale(boolean hasValidatedFields, String lowerCaseLanguageCode,
+ String upperCaseCountryCode) {
this.languageCode = lowerCaseLanguageCode;
this.countryCode = upperCaseCountryCode;
this.variantCode = "";
+ this.scriptCode = "";
+
+ this.unicodeAttributes = Collections.EMPTY_SET;
+ this.unicodeKeywords = Collections.EMPTY_MAP;
+ this.extensions = Collections.EMPTY_MAP;
+
+ this.hasValidatedFields = hasValidatedFields;
}
/**
* Constructs a new {@code Locale} using the specified language.
*/
public Locale(String language) {
- this(language, "", "");
+ this(language, "", "", "", Collections.EMPTY_SET, Collections.EMPTY_MAP,
+ Collections.EMPTY_MAP, false /* has validated fields */);
}
/**
* Constructs a new {@code Locale} using the specified language and country codes.
*/
public Locale(String language, String country) {
- this(language, country, "");
+ this(language, country, "", "", Collections.EMPTY_SET, Collections.EMPTY_MAP,
+ Collections.EMPTY_MAP, false /* has validated fields */);
}
/**
- * Constructs a new {@code Locale} using the specified language, country,
- * and variant codes.
+ * Required by libcore.icu.ICU.
+ *
+ * @hide
*/
- public Locale(String language, String country, String variant) {
+ public Locale(String language, String country, String variant, String scriptCode,
+ /* nonnull */ Set<String> unicodeAttributes,
+ /* nonnull */ Map<String, String> unicodeKeywords,
+ /* nonnull */ Map<Character, String> extensions,
+ boolean hasValidatedFields) {
if (language == null || country == null || variant == null) {
throw new NullPointerException("language=" + language +
- ",country=" + country +
- ",variant=" + variant);
+ ",country=" + country +
+ ",variant=" + variant);
}
- if (language.isEmpty() && country.isEmpty()) {
- languageCode = "";
- countryCode = "";
- variantCode = variant;
- return;
+
+ if (hasValidatedFields) {
+ this.languageCode = adjustLanguageCode(language);
+ this.countryCode = country;
+ this.variantCode = variant;
+ } else {
+ if (language.isEmpty() && country.isEmpty()) {
+ languageCode = "";
+ countryCode = "";
+ variantCode = variant;
+ } else {
+ languageCode = adjustLanguageCode(language);
+ countryCode = country.toUpperCase(Locale.US);
+ variantCode = variant;
+ }
}
- languageCode = language.toLowerCase(Locale.US);
- // Map new language codes to the obsolete language
- // codes so the correct resource bundles will be used.
- if (languageCode.equals("he")) {
- languageCode = "iw";
- } else if (languageCode.equals("id")) {
- languageCode = "in";
- } else if (languageCode.equals("yi")) {
- languageCode = "ji";
+ this.scriptCode = scriptCode;
+
+ if (hasValidatedFields) {
+ Set<String> attribsCopy = new TreeSet<String>(unicodeAttributes);
+ Map<String, String> keywordsCopy = new TreeMap<String, String>(unicodeKeywords);
+ Map<Character, String> extensionsCopy = new TreeMap<Character, String>(extensions);
+
+ // We need to transform the list of attributes & keywords set on the
+ // builder to a unicode locale extension. i.e, if we have any keywords
+ // or attributes set, Locale#getExtension('u') should return a well
+ // formed extension.
+ addUnicodeExtensionToExtensionsMap(attribsCopy, keywordsCopy, extensionsCopy);
+
+ this.unicodeAttributes = Collections.unmodifiableSet(attribsCopy);
+ this.unicodeKeywords = Collections.unmodifiableMap(keywordsCopy);
+ this.extensions = Collections.unmodifiableMap(extensionsCopy);
+ } else {
+ this.unicodeAttributes = unicodeAttributes;
+ this.unicodeKeywords = unicodeKeywords;
+ this.extensions = extensions;
}
- countryCode = country.toUpperCase(Locale.US);
+ this.hasValidatedFields = hasValidatedFields;
+ }
- // Work around for be compatible with RI
- variantCode = variant;
+ /**
+ * Constructs a new {@code Locale} using the specified language, country,
+ * and variant codes.
+ */
+ public Locale(String language, String country, String variant) {
+ this(language, country, variant, "", Collections.EMPTY_SET,
+ Collections.EMPTY_MAP, Collections.EMPTY_MAP,
+ false /* has validated fields */);
}
@Override public Object clone() {
@@ -341,7 +964,10 @@ public final class Locale implements Cloneable, Serializable {
Locale o = (Locale) object;
return languageCode.equals(o.languageCode)
&& countryCode.equals(o.countryCode)
- && variantCode.equals(o.variantCode);
+ && variantCode.equals(o.variantCode)
+ && scriptCode.equals(o.scriptCode)
+ && extensions.equals(o.extensions);
+
}
return false;
}
@@ -399,9 +1025,16 @@ public final class Locale implements Cloneable, Serializable {
if (countryCode.isEmpty()) {
return "";
}
- String result = ICU.getDisplayCountryNative(toString(), locale.toString());
+
+ final String normalizedRegion = Builder.normalizeAndValidateRegion(
+ countryCode, false /* strict */);
+ if (normalizedRegion.isEmpty()) {
+ return countryCode;
+ }
+
+ String result = ICU.getDisplayCountry(this, locale);
if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
- result = ICU.getDisplayCountryNative(toString(), Locale.getDefault().toString());
+ result = ICU.getDisplayCountry(this, Locale.getDefault());
}
return result;
}
@@ -422,17 +1055,24 @@ public final class Locale implements Cloneable, Serializable {
return "";
}
- // http://b/8049507 --- frameworks/base should use fil_PH instead of tl_PH.
- // Until then, we're stuck covering their tracks, making it look like they're
- // using "fil" when they're not.
- String localeString = toString();
- if (languageCode.equals("tl")) {
- localeString = toNewString("fil", countryCode, variantCode);
+ // Hacks for backward compatibility.
+ //
+ // Our language tag will contain "und" if the languageCode is invalid
+ // or missing. ICU will then return "langue indéterminée" or the equivalent
+ // display language for the indeterminate language code.
+ //
+ // Sigh... ugh... and what not.
+ final String normalizedLanguage = Builder.normalizeAndValidateLanguage(
+ languageCode, false /* strict */);
+ if (UNDETERMINED_LANGUAGE.equals(normalizedLanguage)) {
+ return languageCode;
}
- String result = ICU.getDisplayLanguageNative(localeString, locale.toString());
+ // TODO: We need a new hack or a complete fix for http://b/8049507 --- We would
+ // cover the frameworks' tracks when they were using "tl" instead of "fil".
+ String result = ICU.getDisplayLanguage(this, locale);
if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
- result = ICU.getDisplayLanguageNative(localeString, Locale.getDefault().toString());
+ result = ICU.getDisplayLanguage(this, Locale.getDefault());
}
return result;
}
@@ -447,13 +1087,14 @@ public final class Locale implements Cloneable, Serializable {
/**
* Returns this locale's language name, country name, and variant, localized
* to {@code locale}. The exact output form depends on whether this locale
- * corresponds to a specific language, country and variant.
+ * corresponds to a specific language, script, country and variant.
*
* <p>For example:
* <ul>
* <li>{@code new Locale("en").getDisplayName(Locale.US)} -> {@code English}
* <li>{@code new Locale("en", "US").getDisplayName(Locale.US)} -> {@code English (United States)}
* <li>{@code new Locale("en", "US", "POSIX").getDisplayName(Locale.US)} -> {@code English (United States,Computer)}
+ * <li>{@code Locale.fromLanguageTag("zh-Hant-CN").getDisplayName(Locale.US)} -> {@code Chinese (Traditional Han,China)}
* <li>{@code new Locale("en").getDisplayName(Locale.FRANCE)} -> {@code anglais}
* <li>{@code new Locale("en", "US").getDisplayName(Locale.FRANCE)} -> {@code anglais (États-Unis)}
* <li>{@code new Locale("en", "US", "POSIX").getDisplayName(Locale.FRANCE)} -> {@code anglais (États-Unis,informatique)}.
@@ -467,9 +1108,19 @@ public final class Locale implements Cloneable, Serializable {
buffer.append(displayLanguage.isEmpty() ? languageCode : displayLanguage);
++count;
}
+ if (!scriptCode.isEmpty()) {
+ if (count == 1) {
+ buffer.append(" (");
+ }
+ String displayScript = getDisplayScript(locale);
+ buffer.append(displayScript.isEmpty() ? scriptCode : displayScript);
+ ++count;
+ }
if (!countryCode.isEmpty()) {
if (count == 1) {
buffer.append(" (");
+ } else if (count == 2) {
+ buffer.append(",");
}
String displayCountry = getDisplayCountry(locale);
buffer.append(displayCountry.isEmpty() ? countryCode : displayCountry);
@@ -478,7 +1129,7 @@ public final class Locale implements Cloneable, Serializable {
if (!variantCode.isEmpty()) {
if (count == 1) {
buffer.append(" (");
- } else if (count == 2) {
+ } else if (count == 2 || count == 3) {
buffer.append(",");
}
String displayVariant = getDisplayVariant(locale);
@@ -495,6 +1146,8 @@ public final class Locale implements Cloneable, Serializable {
* Returns the full variant name in the default {@code Locale} for the variant code of
* this {@code Locale}. If there is no matching variant name, the variant code is
* returned.
+ *
+ * @since 1.7
*/
public final String getDisplayVariant() {
return getDisplayVariant(getDefault());
@@ -504,14 +1157,31 @@ public final class Locale implements Cloneable, Serializable {
* Returns the full variant name in the specified {@code Locale} for the variant code
* of this {@code Locale}. If there is no matching variant name, the variant code is
* returned.
+ *
+ * @since 1.7
*/
public String getDisplayVariant(Locale locale) {
- if (variantCode.length() == 0) {
+ if (variantCode.isEmpty()) {
+ return "";
+ }
+
+ try {
+ Builder.normalizeAndValidateVariant(variantCode);
+ } catch (IllformedLocaleException ilfe) {
return variantCode;
}
- String result = ICU.getDisplayVariantNative(toString(), locale.toString());
+
+ String result = ICU.getDisplayVariant(this, locale);
if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
- result = ICU.getDisplayVariantNative(toString(), Locale.getDefault().toString());
+ result = ICU.getDisplayVariant(this, Locale.getDefault());
+ }
+
+ // The "old style" locale constructors allow us to pass in variants that aren't
+ // valid BCP-47 variant subtags. When that happens, toLanguageTag will not emit
+ // them. Note that we know variantCode.length() > 0 due to the isEmpty check at
+ // the beginning of this function.
+ if (result.isEmpty()) {
+ return variantCode;
}
return result;
}
@@ -522,7 +1192,10 @@ public final class Locale implements Cloneable, Serializable {
* @throws MissingResourceException if there's no 3-letter country code for this locale.
*/
public String getISO3Country() {
- String code = ICU.getISO3CountryNative(toString());
+ // The results of getISO3Country do not depend on the languageCode,
+ // so we pass an arbitrarily selected language code here. This guards
+ // against errors caused by malformed or invalid language codes.
+ String code = ICU.getISO3Country("en-" + countryCode);
if (!countryCode.isEmpty() && code.isEmpty()) {
throw new MissingResourceException("No 3-letter country code for locale: " + this, "FormatData_" + this, "ShortCountry");
}
@@ -535,7 +1208,16 @@ public final class Locale implements Cloneable, Serializable {
* @throws MissingResourceException if there's no 3-letter language code for this locale.
*/
public String getISO3Language() {
- String code = ICU.getISO3LanguageNative(toString());
+ // For backward compatibility, we must return "" for an empty language
+ // code and not "und" which is the accurate ISO-639-3 code for an
+ // undetermined language.
+ if (languageCode.isEmpty()) {
+ return "";
+ }
+
+ // The results of getISO3Language do not depend on the country code
+ // or any of the other locale fields, so we pass just the language here.
+ String code = ICU.getISO3Language(languageCode);
if (!languageCode.isEmpty() && code.isEmpty()) {
throw new MissingResourceException("No 3-letter language code for locale: " + this, "FormatData_" + this, "ShortLanguage");
}
@@ -574,10 +1256,335 @@ public final class Locale implements Cloneable, Serializable {
return variantCode;
}
+ /**
+ * Returns the script code for this {@code Locale} or an empty {@code String} if no script
+ * was set.
+ *
+ * If set, the script code will be a title cased string of length 4, as per the ISO 15924
+ * specification.
+ *
+ * @since 1.7
+ */
+ public String getScript() {
+ return scriptCode;
+ }
+
+ /**
+ * Equivalent to {@code getDisplayScript(Locale.getDefault()))}
+ *
+ * @since 1.7
+ */
+ public String getDisplayScript() {
+ return getDisplayScript(getDefault());
+ }
+
+ /**
+ * Returns the name of this locale's script code, localized to {@link Locale}. If the
+ * script code is unknown, the return value of this method is the same as that of
+ * {@link #getScript()}.
+ *
+ * @since 1.7
+ */
+ public String getDisplayScript(Locale locale) {
+ if (scriptCode.isEmpty()) {
+ return "";
+ }
+
+ String result = ICU.getDisplayScript(this, locale);
+ if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
+ result = ICU.getDisplayScript(this, Locale.getDefault());
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Returns a well formed BCP-47 language tag that identifies this locale.
+ *
+ * Note that this locale itself might consist of ill formed fields, since the
+ * public {@code Locale} constructors do not perform validity checks to maintain
+ * backwards compatibility. When this is the case, this method will either replace
+ * ill formed fields with standard BCP-47 subtags (For eg. "und" (undetermined)
+ * for invalid languages) or omit them altogether.
+ *
+ * Additionally, ill formed variants will result in the remainder of the tag
+ * (both variants and extensions) being moved to the private use extension,
+ * where they will appear after a subtag whose value is {@code "lvariant"}.
+ *
+ * It's also important to note that the BCP-47 tag is well formed in the sense
+ * that it is unambiguously parseable into its specified components. We do not
+ * require that any of the components are registered with the applicable registries.
+ * For example, we do not require scripts to be a registered ISO 15924 scripts or
+ * languages to appear in the ISO-639-2 code list.
+ *
+ * @since 1.7
+ */
+ public String toLanguageTag() {
+ if (cachedLanguageTag == null) {
+ cachedLanguageTag = makeLanguageTag();
+ }
+
+ return cachedLanguageTag;
+ }
+
+ /**
+ * Constructs a valid BCP-47 language tag from locale fields. Additional validation
+ * is required when this Locale was not constructed using a Builder and variants
+ * set this way are treated specially.
+ *
+ * In both cases, we convert empty language tags to "und", omit invalid country tags
+ * and perform a special case conversion of "no-NO-NY" to "nn-NO".
+ */
+ private String makeLanguageTag() {
+ // We only need to revalidate the language, country and variant because
+ // the rest of the fields can only be set via the builder which validates
+ // them anyway.
+ String language = "";
+ String region = "";
+ String variant = "";
+ String illFormedVariantSubtags = "";
+
+ if (hasValidatedFields) {
+ language = languageCode;
+ region = countryCode;
+ // Note that we are required to normalize hyphens to underscores
+ // in the builder, but we must use hyphens in the BCP-47 language tag.
+ variant = variantCode.replace('_', '-');
+ } else {
+ language = Builder.normalizeAndValidateLanguage(languageCode, false /* strict */);
+ region = Builder.normalizeAndValidateRegion(countryCode, false /* strict */);
+
+ try {
+ variant = Builder.normalizeAndValidateVariant(variantCode);
+ } catch (IllformedLocaleException ilfe) {
+ // If our variant is ill formed, we must attempt to split it into
+ // its constituent subtags and preserve the well formed bits and
+ // move the rest to the private use extension (if they're well
+ // formed extension subtags).
+ String split[] = splitIllformedVariant(variantCode);
+
+ variant = split[0];
+ illFormedVariantSubtags = split[1];
+ }
+ }
+
+ if (language.isEmpty()) {
+ language = UNDETERMINED_LANGUAGE;
+ }
+
+ if ("no".equals(language) && "NO".equals(region) && "NY".equals(variant)) {
+ language = "nn";
+ region = "NO";
+ variant = "";
+ }
+
+ final StringBuilder sb = new StringBuilder(16);
+ sb.append(language);
+
+ if (!scriptCode.isEmpty()) {
+ sb.append('-');
+ sb.append(scriptCode);
+ }
+
+ if (!region.isEmpty()) {
+ sb.append('-');
+ sb.append(region);
+ }
+
+ if (!variant.isEmpty()) {
+ sb.append('-');
+ sb.append(variant);
+ }
+
+ // Extensions (optional, omitted if empty). Note that we don't
+ // emit the private use extension here, but add it in the end.
+ for (Map.Entry<Character, String> extension : extensions.entrySet()) {
+ if (!extension.getKey().equals('x')) {
+ sb.append('-').append(extension.getKey());
+ sb.append('-').append(extension.getValue());
+ }
+ }
+
+ // The private use extension comes right at the very end.
+ final String privateUse = extensions.get('x');
+ if (privateUse != null) {
+ sb.append("-x-");
+ sb.append(privateUse);
+ }
+
+ // If we have any ill-formed variant subtags, we append them to the
+ // private use extension (or add a private use extension if one doesn't
+ // exist).
+ if (!illFormedVariantSubtags.isEmpty()) {
+ if (privateUse == null) {
+ sb.append("-x-lvariant-");
+ } else {
+ sb.append('-');
+ }
+ sb.append(illFormedVariantSubtags);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Splits ill formed variants into a set of valid variant subtags (which
+ * can be used directly in language tag construction) and a set of invalid
+ * variant subtags (which can be appended to the private use extension),
+ * provided that each subtag is a valid private use extension subtag.
+ *
+ * This method returns a two element String array. The first element is a string
+ * containing the concatenation of valid variant subtags which can be appended
+ * to a BCP-47 tag directly and the second containing the concatenation of
+ * invalid variant subtags which can be appended to the private use extension
+ * directly.
+ *
+ * This method assumes that {@code variant} contains at least one ill formed
+ * variant subtag.
+ */
+ private static String[] splitIllformedVariant(String variant) {
+ final String normalizedVariant = variant.replace('_', '-');
+ final String[] subTags = normalizedVariant.split("-");
+
+ final String[] split = new String[] { "", "" };
+
+ // First go through the list of variant subtags and check if they're
+ // valid private use extension subtags. If they're not, we will omit
+ // the first such subtag and all subtags after.
+ //
+ // NOTE: |firstInvalidSubtag| is the index of the first variant
+ // subtag we decide to omit altogether, whereas |firstIllformedSubtag| is the
+ // index of the first subtag we decide to append to the private use extension.
+ //
+ // In other words:
+ // [0, firstIllformedSubtag) => expressed as variant subtags.
+ // [firstIllformedSubtag, firstInvalidSubtag) => expressed as private use
+ // extension subtags.
+ // [firstInvalidSubtag, subTags.length) => omitted.
+ int firstInvalidSubtag = subTags.length;
+ for (int i = 0; i < subTags.length; ++i) {
+ if (!isValidBcp47Alphanum(subTags[i], 1, 8)) {
+ firstInvalidSubtag = i;
+ break;
+ }
+ }
+
+ if (firstInvalidSubtag == 0) {
+ return split;
+ }
+
+ // We now consider each subtag that could potentially be appended to
+ // the private use extension and check if it's valid.
+ int firstIllformedSubtag = firstInvalidSubtag;
+ for (int i = 0; i < firstInvalidSubtag; ++i) {
+ final String subTag = subTags[i];
+ // The BCP-47 spec states that :
+ // - Subtags can be between [5, 8] alphanumeric chars in length.
+ // - Subtags that start with a number are allowed to be 4 chars in length.
+ if (subTag.length() >= 5 && subTag.length() <= 8) {
+ if (!isAsciiAlphaNum(subTag)) {
+ firstIllformedSubtag = i;
+ }
+ } else if (subTag.length() == 4) {
+ final char firstChar = subTag.charAt(0);
+ if (!(firstChar >= '0' && firstChar <= '9') || !isAsciiAlphaNum(subTag)) {
+ firstIllformedSubtag = i;
+ }
+ } else {
+ firstIllformedSubtag = i;
+ }
+ }
+
+ split[0] = concatenateRange(subTags, 0, firstIllformedSubtag);
+ split[1] = concatenateRange(subTags, firstIllformedSubtag, firstInvalidSubtag);
+
+ return split;
+ }
+
+ /**
+ * Builds a string by concatenating array elements within the range [start, end).
+ * The supplied range is assumed to be valid and no checks are performed.
+ */
+ private static String concatenateRange(String[] array, int start, int end) {
+ StringBuilder builder = new StringBuilder(32);
+ for (int i = start; i < end; ++i) {
+ if (i != start) {
+ builder.append('-');
+ }
+ builder.append(array[i]);
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Returns the set of BCP-47 extensions this locale contains.
+ *
+ * See <a href="https://tools.ietf.org/html/bcp47#section-2.1">
+ * the IETF BCP-47 specification</a> (Section 2.2.6) for details.
+ *
+ * @since 1.7
+ */
+ public Set<Character> getExtensionKeys() {
+ return extensions.keySet();
+ }
+
+ /**
+ * Returns the BCP-47 extension whose key is {@code extensionKey}, or {@code null}
+ * if this locale does not contain the extension.
+ *
+ * Individual Keywords and attributes for the unicode
+ * locale extension can be fetched using {@link #getUnicodeLocaleAttributes()},
+ * {@link #getUnicodeLocaleKeys()} and {@link #getUnicodeLocaleType}.
+ *
+ * @since 1.7
+ */
+ public String getExtension(char extensionKey) {
+ return extensions.get(extensionKey);
+ }
+
+ /**
+ * Returns the {@code type} for the specified unicode locale extension {@code key}.
+ *
+ * For more information about types and keywords, see {@link Builder#setUnicodeLocaleKeyword}
+ * and <a href="http://www.unicode.org/reports/tr35/#BCP47">Unicode Technical Standard #35</a>
+ *
+ * @since 1.7
+ */
+ public String getUnicodeLocaleType(String keyWord) {
+ return unicodeKeywords.get(keyWord);
+ }
+
+ /**
+ * Returns the set of unicode locale extension attributes this locale contains.
+ *
+ * For more information about attributes, see {@link Builder#addUnicodeLocaleAttribute}
+ * and <a href="http://www.unicode.org/reports/tr35/#BCP47">Unicode Technical Standard #35</a>
+ *
+ * @since 1.7
+ */
+ public Set<String> getUnicodeLocaleAttributes() {
+ return unicodeAttributes;
+ }
+
+ /**
+ * Returns the set of unicode locale extension keywords this locale contains.
+ *
+ * For more information about types and keywords, see {@link Builder#setUnicodeLocaleKeyword}
+ * and <a href="http://www.unicode.org/reports/tr35/#BCP47">Unicode Technical Standard #35</a>
+ *
+ * @since 1.7
+ */
+ public Set<String> getUnicodeLocaleKeys() {
+ return unicodeKeywords.keySet();
+ }
+
@Override
public synchronized int hashCode() {
- return countryCode.hashCode() + languageCode.hashCode()
- + variantCode.hashCode();
+ return countryCode.hashCode()
+ + languageCode.hashCode() + variantCode.hashCode()
+ + scriptCode.hashCode() + extensions.hashCode();
}
/**
@@ -592,7 +1599,9 @@ public final class Locale implements Cloneable, Serializable {
if (locale == null) {
throw new NullPointerException("locale == null");
}
+ String languageTag = locale.toLanguageTag();
defaultLocale = locale;
+ ICU.setDefaultLocale(languageTag);
}
/**
@@ -610,30 +1619,59 @@ public final class Locale implements Cloneable, Serializable {
public final String toString() {
String result = cachedToStringResult;
if (result == null) {
- result = cachedToStringResult = toNewString(languageCode, countryCode, variantCode);
+ result = cachedToStringResult = toNewString(languageCode, countryCode, variantCode,
+ scriptCode, extensions);
}
return result;
}
- private static String toNewString(String languageCode, String countryCode, String variantCode) {
+ private static String toNewString(String languageCode, String countryCode,
+ String variantCode, String scriptCode, Map<Character, String> extensions) {
// The string form of a locale that only has a variant is the empty string.
if (languageCode.length() == 0 && countryCode.length() == 0) {
return "";
}
+
// Otherwise, the output format is "ll_cc_variant", where language and country are always
// two letters, but the variant is an arbitrary length. A size of 11 characters has room
// for "en_US_POSIX", the largest "common" value. (In practice, the string form is almost
// always 5 characters: "ll_cc".)
StringBuilder result = new StringBuilder(11);
result.append(languageCode);
- if (countryCode.length() > 0 || variantCode.length() > 0) {
+
+ final boolean hasScriptOrExtensions = !scriptCode.isEmpty() || !extensions.isEmpty();
+
+ if (!countryCode.isEmpty() || !variantCode.isEmpty() || hasScriptOrExtensions) {
result.append('_');
}
result.append(countryCode);
- if (variantCode.length() > 0) {
+ if (!variantCode.isEmpty() || hasScriptOrExtensions) {
result.append('_');
}
result.append(variantCode);
+
+ if (hasScriptOrExtensions) {
+ if (!variantCode.isEmpty()) {
+ result.append('_');
+ }
+
+ // Note that this is notably different from the BCP-47 spec (for
+ // backwards compatibility). We are forced to append a "#" before the script tag.
+ // and also put the script code right at the end.
+ result.append("#");
+ if (!scriptCode.isEmpty() ) {
+ result.append(scriptCode);
+ }
+
+ // Note the use of "-" instead of "_" before the extensions.
+ if (!extensions.isEmpty()) {
+ if (!scriptCode.isEmpty()) {
+ result.append('-');
+ }
+ result.append(serializeExtensions(extensions));
+ }
+ }
+
return result.toString();
}
@@ -642,6 +1680,8 @@ public final class Locale implements Cloneable, Serializable {
new ObjectStreamField("hashcode", int.class),
new ObjectStreamField("language", String.class),
new ObjectStreamField("variant", String.class),
+ new ObjectStreamField("script", String.class),
+ new ObjectStreamField("extensions", String.class),
};
private void writeObject(ObjectOutputStream stream) throws IOException {
@@ -650,6 +1690,12 @@ public final class Locale implements Cloneable, Serializable {
fields.put("hashcode", -1);
fields.put("language", languageCode);
fields.put("variant", variantCode);
+ fields.put("script", scriptCode);
+
+ if (!extensions.isEmpty()) {
+ fields.put("extensions", serializeExtensions(extensions));
+ }
+
stream.writeFields();
}
@@ -658,5 +1704,545 @@ public final class Locale implements Cloneable, Serializable {
countryCode = (String) fields.get("country", "");
languageCode = (String) fields.get("language", "");
variantCode = (String) fields.get("variant", "");
+ scriptCode = (String) fields.get("script", "");
+
+ this.unicodeKeywords = Collections.EMPTY_MAP;
+ this.unicodeAttributes = Collections.EMPTY_SET;
+ this.extensions = Collections.EMPTY_MAP;
+
+ String extensions = (String) fields.get("extensions", null);
+ if (extensions != null) {
+ readExtensions(extensions);
+ }
+ }
+
+ private void readExtensions(String extensions) {
+ Map<Character, String> extensionsMap = new TreeMap<Character, String>();
+ parseSerializedExtensions(extensions, extensionsMap);
+ this.extensions = Collections.unmodifiableMap(extensionsMap);
+
+ if (extensionsMap.containsKey(UNICODE_LOCALE_EXTENSION)) {
+ String unicodeExtension = extensionsMap.get(UNICODE_LOCALE_EXTENSION);
+ String[] subTags = unicodeExtension.split("-");
+
+ Map<String, String> unicodeKeywords = new TreeMap<String, String>();
+ Set<String> unicodeAttributes = new TreeSet<String>();
+ parseUnicodeExtension(subTags, unicodeKeywords, unicodeAttributes);
+
+ this.unicodeKeywords = Collections.unmodifiableMap(unicodeKeywords);
+ this.unicodeAttributes = Collections.unmodifiableSet(unicodeAttributes);
+ }
+ }
+
+ /**
+ * The serialized form for extensions is straightforward. It's simply
+ * of the form key1-value1-key2-value2 where each value might in turn contain
+ * multiple subtags separated by hyphens. Each key is guaranteed to be a single
+ * character in length.
+ *
+ * This method assumes that {@code extensionsMap} is non-empty.
+ *
+ * Visible for testing.
+ *
+ * @hide
+ */
+ public static String serializeExtensions(Map<Character, String> extensionsMap) {
+ Iterator<Map.Entry<Character, String>> entryIterator = extensionsMap.entrySet().iterator();
+ StringBuilder sb = new StringBuilder(64);
+
+ while (true) {
+ final Map.Entry<Character, String> entry = entryIterator.next();
+ sb.append(entry.getKey());
+ sb.append('-');
+ sb.append(entry.getValue());
+
+ if (entryIterator.hasNext()) {
+ sb.append('-');
+ } else {
+ break;
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Visible for testing.
+ *
+ * @hide
+ */
+ public static void parseSerializedExtensions(String extString, Map<Character, String> outputMap) {
+ // This probably isn't the most efficient approach, but it's the
+ // most straightforward to code.
+ //
+ // Start by splitting the string on "-". We will then keep track of
+ // where each of the extension keys (single characters) appear in the
+ // original string and then use those indices to construct substrings
+ // representing the values.
+ final String[] subTags = extString.split("-");
+ final int[] typeStartIndices = new int[subTags.length / 2];
+
+ int length = 0;
+ int count = 0;
+ for (String subTag : subTags) {
+ if (subTag.length() > 0) {
+ // Account for the length of the "-" at the end of each subtag.
+ length += (subTag.length() + 1);
+ }
+
+ if (subTag.length() == 1) {
+ typeStartIndices[count++] = length;
+ }
+ }
+
+ for (int i = 0; i < count; ++i) {
+ final int valueStart = typeStartIndices[i];
+ // Since the start Index points to the beginning of the next type
+ // ....prev-k-next.....
+ // |_ here
+ // (idx - 2) is the index of the next key
+ // (idx - 3) is the (non inclusive) end of the previous type.
+ final int valueEnd = (i == (count - 1)) ?
+ extString.length() : (typeStartIndices[i + 1] - 3);
+
+ outputMap.put(extString.charAt(typeStartIndices[i] - 2),
+ extString.substring(valueStart, valueEnd));
+ }
+ }
+
+
+ /**
+ * A UN M.49 is a 3 digit numeric code.
+ */
+ private static boolean isUnM49AreaCode(String code) {
+ if (code.length() != 3) {
+ return false;
+ }
+
+ for (int i = 0; i < 3; ++i) {
+ final char character = code.charAt(i);
+ if (!(character >= '0' && character <= '9')) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /*
+ * Checks whether a given string is an ASCII alphanumeric string.
+ */
+ private static boolean isAsciiAlphaNum(String string) {
+ for (int i = 0; i < string.length(); i++) {
+ final char character = string.charAt(i);
+ if (!(character >= 'a' && character <= 'z' ||
+ character >= 'A' && character <= 'Z' ||
+ character >= '0' && character <= '9')) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean isValidBcp47Alpha(String string, int lowerBound, int upperBound) {
+ final int length = string.length();
+ if (length < lowerBound || length > upperBound) {
+ return false;
+ }
+
+ for (int i = 0; i < length; ++i) {
+ final char character = string.charAt(i);
+ if (!(character >= 'a' && character <= 'z' ||
+ character >= 'A' && character <= 'Z')) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean isValidBcp47Alphanum(String attributeOrType,
+ int lowerBound, int upperBound) {
+ if (attributeOrType.length() < lowerBound || attributeOrType.length() > upperBound) {
+ return false;
+ }
+
+ return isAsciiAlphaNum(attributeOrType);
+ }
+
+ private static String titleCaseAsciiWord(String word) {
+ try {
+ byte[] chars = word.toLowerCase(Locale.ROOT).getBytes(StandardCharsets.US_ASCII);
+ chars[0] = (byte) ((int) chars[0] + 'A' - 'a');
+ return new String(chars, StandardCharsets.US_ASCII);
+ } catch (UnsupportedOperationException uoe) {
+ throw new AssertionError(uoe);
+ }
+ }
+
+ /**
+ * A type list must contain one or more alphanumeric subtags whose lengths
+ * are between 3 and 8.
+ */
+ private static boolean isValidTypeList(String lowerCaseTypeList) {
+ final String[] splitList = lowerCaseTypeList.split("-");
+ for (String type : splitList) {
+ if (!isValidBcp47Alphanum(type, 3, 8)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static void addUnicodeExtensionToExtensionsMap(
+ Set<String> attributes, Map<String, String> keywords,
+ Map<Character, String> extensions) {
+ if (attributes.isEmpty() && keywords.isEmpty()) {
+ return;
+ }
+
+ // Assume that the common case is a low number of keywords & attributes
+ // (usually one or two).
+ final StringBuilder sb = new StringBuilder(32);
+
+ // All attributes must appear before keywords, in lexical order.
+ if (!attributes.isEmpty()) {
+ Iterator<String> attributesIterator = attributes.iterator();
+ while (true) {
+ sb.append(attributesIterator.next());
+ if (attributesIterator.hasNext()) {
+ sb.append('-');
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (!keywords.isEmpty()) {
+ if (!attributes.isEmpty()) {
+ sb.append('-');
+ }
+
+ Iterator<Map.Entry<String, String>> keywordsIterator = keywords.entrySet().iterator();
+ while (true) {
+ final Map.Entry<String, String> keyWord = keywordsIterator.next();
+ sb.append(keyWord.getKey());
+ if (!keyWord.getValue().isEmpty()) {
+ sb.append('-');
+ sb.append(keyWord.getValue());
+ }
+ if (keywordsIterator.hasNext()) {
+ sb.append('-');
+ } else {
+ break;
+ }
+ }
+ }
+
+ extensions.put(UNICODE_LOCALE_EXTENSION, sb.toString());
+ }
+
+ /**
+ * This extension is described by http://www.unicode.org/reports/tr35/#RFC5234
+ * unicode_locale_extensions = sep "u" (1*(sep keyword) / 1*(sep attribute) *(sep keyword)).
+ *
+ * It must contain at least one keyword or attribute and attributes (if any)
+ * must appear before keywords. Attributes can't appear after keywords because
+ * they will be indistinguishable from a subtag of the keyword type.
+ *
+ * Visible for testing.
+ *
+ * @hide
+ */
+ public static void parseUnicodeExtension(String[] subtags,
+ Map<String, String> keywords, Set<String> attributes) {
+ String lastKeyword = null;
+ List<String> subtagsForKeyword = new ArrayList<String>();
+ for (String subtag : subtags) {
+ if (subtag.length() == 2) {
+ if (subtagsForKeyword.size() > 0) {
+ keywords.put(lastKeyword, joinBcp47Subtags(subtagsForKeyword));
+ subtagsForKeyword.clear();
+ }
+
+ lastKeyword = subtag;
+ } else if (subtag.length() > 2) {
+ if (lastKeyword == null) {
+ attributes.add(subtag);
+ } else {
+ subtagsForKeyword.add(subtag);
+ }
+ }
+ }
+
+ if (subtagsForKeyword.size() > 0) {
+ keywords.put(lastKeyword, joinBcp47Subtags(subtagsForKeyword));
+ } else if (lastKeyword != null) {
+ keywords.put(lastKeyword, "");
+ }
+ }
+
+ /**
+ * Joins a list of subtags into a BCP-47 tag using the standard separator
+ * ("-").
+ */
+ private static String joinBcp47Subtags(List<String> strings) {
+ final int size = strings.size();
+
+ StringBuilder sb = new StringBuilder(strings.get(0).length());
+ for (int i = 0; i < size; ++i) {
+ sb.append(strings.get(i));
+ if (i != size - 1) {
+ sb.append('-');
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * @hide for internal use only.
+ */
+ public static String adjustLanguageCode(String languageCode) {
+ String adjusted = languageCode.toLowerCase(Locale.US);
+ // Map new language codes to the obsolete language
+ // codes so the correct resource bundles will be used.
+ if (languageCode.equals("he")) {
+ adjusted = "iw";
+ } else if (languageCode.equals("id")) {
+ adjusted = "in";
+ } else if (languageCode.equals("yi")) {
+ adjusted = "ji";
+ }
+
+ return adjusted;
+ }
+
+ /**
+ * Map of grandfathered language tags to their modern replacements.
+ */
+ private static final TreeMap<String, String> GRANDFATHERED_LOCALES;
+
+ static {
+ GRANDFATHERED_LOCALES = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
+
+ // From http://tools.ietf.org/html/bcp47
+ //
+ // grandfathered = irregular ; non-redundant tags registered
+ // / regular ; during the RFC 3066 era
+ // irregular =
+ GRANDFATHERED_LOCALES.put("en-GB-oed", "en-GB-x-oed");
+ GRANDFATHERED_LOCALES.put("i-ami", "ami");
+ GRANDFATHERED_LOCALES.put("i-bnn", "bnn");
+ GRANDFATHERED_LOCALES.put("i-default", "en-x-i-default");
+ GRANDFATHERED_LOCALES.put("i-enochian", "und-x-i-enochian");
+ GRANDFATHERED_LOCALES.put("i-hak", "hak");
+ GRANDFATHERED_LOCALES.put("i-klingon", "tlh");
+ GRANDFATHERED_LOCALES.put("i-lux", "lb");
+ GRANDFATHERED_LOCALES.put("i-mingo", "see-x-i-mingo");
+ GRANDFATHERED_LOCALES.put("i-navajo", "nv");
+ GRANDFATHERED_LOCALES.put("i-pwn", "pwn");
+ GRANDFATHERED_LOCALES.put("i-tao", "tao");
+ GRANDFATHERED_LOCALES.put("i-tay", "tay");
+ GRANDFATHERED_LOCALES.put("i-tsu", "tsu");
+ GRANDFATHERED_LOCALES.put("sgn-BE-FR", "sfb");
+ GRANDFATHERED_LOCALES.put("sgn-BE-NL", "vgt");
+ GRANDFATHERED_LOCALES.put("sgn-CH-DE", "sgg");
+
+ // regular =
+ GRANDFATHERED_LOCALES.put("art-lojban", "jbo");
+ GRANDFATHERED_LOCALES.put("cel-gaulish", "xtg-x-cel-gaulish");
+ GRANDFATHERED_LOCALES.put("no-bok", "nb");
+ GRANDFATHERED_LOCALES.put("no-nyn", "nn");
+ GRANDFATHERED_LOCALES.put("zh-guoyu", "cmn");
+ GRANDFATHERED_LOCALES.put("zh-hakka", "hak");
+ GRANDFATHERED_LOCALES.put("zh-min", "nan-x-zh-min");
+ GRANDFATHERED_LOCALES.put("zh-min-nan", "nan");
+ GRANDFATHERED_LOCALES.put("zh-xiang", "hsn");
+ }
+
+ private static String convertGrandfatheredTag(String original) {
+ final String converted = GRANDFATHERED_LOCALES.get(original);
+ return converted != null ? converted : original;
+ }
+
+ /**
+ * Scans elements of {@code subtags} in the range {@code [startIndex, endIndex)}
+ * and appends valid variant subtags upto the first invalid subtag (if any) to
+ * {@code normalizedVariants}.
+ */
+ private static void extractVariantSubtags(String[] subtags, int startIndex, int endIndex,
+ List<String> normalizedVariants) {
+ for (int i = startIndex; i < endIndex; i++) {
+ final String subtag = subtags[i];
+
+ if (Builder.isValidVariantSubtag(subtag)) {
+ normalizedVariants.add(subtag);
+ } else {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Scans elements of {@code subtags} in the range {@code [startIndex, endIndex)}
+ * and inserts valid extensions into {@code extensions}. The scan is aborted
+ * when an invalid extension is encountered. Returns the index of the first
+ * unparsable element of {@code subtags}.
+ */
+ private static int extractExtensions(String[] subtags, int startIndex, int endIndex,
+ Map<Character, String> extensions) {
+ int privateUseExtensionIndex = -1;
+ int extensionKeyIndex = -1;
+
+ int i = startIndex;
+ for (; i < endIndex; i++) {
+ final String subtag = subtags[i];
+
+ final boolean parsingPrivateUse = (privateUseExtensionIndex != -1) &&
+ (extensionKeyIndex == privateUseExtensionIndex);
+
+ // Note that private use extensions allow subtags of length 1.
+ // Private use extensions *must* come last, so there's no ambiguity
+ // in that case.
+ if (subtag.length() == 1 && !parsingPrivateUse) {
+ // Emit the last extension we encountered if any. First check
+ // whether we encountered two keys in a row (which is an error).
+ // Also checks if we already have an extension with the same key,
+ // which is again an error.
+ if (extensionKeyIndex != -1) {
+ if ((i - 1) == extensionKeyIndex) {
+ return extensionKeyIndex;
+ }
+
+ final String key = subtags[extensionKeyIndex];
+ if (extensions.containsKey(key.charAt(0))) {
+ return extensionKeyIndex;
+ }
+
+ final String value = concatenateRange(subtags, extensionKeyIndex + 1, i);
+ extensions.put(key.charAt(0), value.toLowerCase(Locale.ROOT));
+ }
+
+ // Mark the start of the next extension. Also keep track of whether this
+ // is a private use extension, and throw an error if it doesn't come last.
+ extensionKeyIndex = i;
+ if ("x".equals(subtag)) {
+ privateUseExtensionIndex = i;
+ } else if (privateUseExtensionIndex != -1) {
+ // The private use extension must come last.
+ return privateUseExtensionIndex;
+ }
+ } else if (extensionKeyIndex != -1) {
+ // We must have encountered a valid key in order to start parsing
+ // its subtags.
+ if (!isValidBcp47Alphanum(subtag, parsingPrivateUse ? 1 : 2, 8)) {
+ return i;
+ }
+ } else {
+ // Encountered a value without a preceding key.
+ return i;
+ }
+ }
+
+ if (extensionKeyIndex != -1) {
+ if ((i - 1) == extensionKeyIndex) {
+ return extensionKeyIndex;
+ }
+
+ final String key = subtags[extensionKeyIndex];
+ if (extensions.containsKey(key.charAt(0))) {
+ return extensionKeyIndex;
+ }
+
+ final String value = concatenateRange(subtags, extensionKeyIndex + 1, i);
+ extensions.put(key.charAt(0), value.toLowerCase(Locale.ROOT));
+ }
+
+ return i;
+ }
+
+ private static Locale forLanguageTag(/* @Nonnull */ String tag, boolean strict) {
+ final String converted = convertGrandfatheredTag(tag);
+ final String[] subtags = converted.split("-");
+
+ int lastSubtag = subtags.length;
+ for (int i = 0; i < subtags.length; ++i) {
+ final String subtag = subtags[i];
+ if (subtag.isEmpty() || subtag.length() > 8) {
+ if (strict) {
+ throw new IllformedLocaleException("Invalid subtag at index: " + i
+ + " in tag: " + tag);
+ } else {
+ lastSubtag = (i - 1);
+ }
+
+ break;
+ }
+ }
+
+ final String languageCode = Builder.normalizeAndValidateLanguage(subtags[0], strict);
+ String scriptCode = "";
+ int nextSubtag = 1;
+ if (lastSubtag > nextSubtag) {
+ scriptCode = Builder.normalizeAndValidateScript(subtags[nextSubtag], false /* strict */);
+ if (!scriptCode.isEmpty()) {
+ nextSubtag++;
+ }
+ }
+
+ String regionCode = "";
+ if (lastSubtag > nextSubtag) {
+ regionCode = Builder.normalizeAndValidateRegion(subtags[nextSubtag], false /* strict */);
+ if (!regionCode.isEmpty()) {
+ nextSubtag++;
+ }
+ }
+
+ List<String> variants = null;
+ if (lastSubtag > nextSubtag) {
+ variants = new ArrayList<String>();
+ extractVariantSubtags(subtags, nextSubtag, lastSubtag, variants);
+ nextSubtag += variants.size();
+ }
+
+ Map<Character, String> extensions = Collections.EMPTY_MAP;
+ if (lastSubtag > nextSubtag) {
+ extensions = new TreeMap<Character, String>();
+ nextSubtag = extractExtensions(subtags, nextSubtag, lastSubtag, extensions);
+ }
+
+ if (nextSubtag != lastSubtag) {
+ if (strict) {
+ throw new IllformedLocaleException("Unparseable subtag: " + subtags[nextSubtag]
+ + " from language tag: " + tag);
+ }
+ }
+
+ Set<String> unicodeKeywords = Collections.EMPTY_SET;
+ Map<String, String> unicodeAttributes = Collections.EMPTY_MAP;
+ if (extensions.containsKey(UNICODE_LOCALE_EXTENSION)) {
+ unicodeKeywords = new TreeSet<String>();
+ unicodeAttributes = new TreeMap<String, String>();
+ parseUnicodeExtension(extensions.get(UNICODE_LOCALE_EXTENSION).split("-"),
+ unicodeAttributes, unicodeKeywords);
+ }
+
+ String variantCode = "";
+ if (variants != null && !variants.isEmpty()) {
+ StringBuilder variantsBuilder = new StringBuilder(variants.size() * 8);
+ for (int i = 0; i < variants.size(); ++i) {
+ if (i != 0) {
+ variantsBuilder.append('_');
+ }
+ variantsBuilder.append(variants.get(i));
+ }
+ variantCode = variantsBuilder.toString();
+ }
+
+ return new Locale(languageCode, regionCode, variantCode, scriptCode,
+ unicodeKeywords, unicodeAttributes, extensions, true /* has validated fields */);
}
}
diff --git a/luni/src/main/java/java/util/Properties.java b/luni/src/main/java/java/util/Properties.java
index cd19295..532d35c 100644
--- a/luni/src/main/java/java/util/Properties.java
+++ b/luni/src/main/java/java/util/Properties.java
@@ -52,7 +52,7 @@ import org.xml.sax.SAXParseException;
* values to be used when a given key is not found in this {@code Properties}
* instance.
*
- * <a name="character_encoding"><h3>Character Encoding</h3></a>
+ * <a name="character_encoding"></a><h3>Character Encoding</h3>
* <p>Note that in some cases {@code Properties} uses ISO-8859-1 instead of UTF-8.
* ISO-8859-1 is only capable of representing a tiny subset of Unicode.
* Use either the {@code loadFromXML}/{@code storeToXML} methods (which use UTF-8 by
diff --git a/luni/src/main/java/java/util/Random.java b/luni/src/main/java/java/util/Random.java
index 4a67244..091d584 100644
--- a/luni/src/main/java/java/util/Random.java
+++ b/luni/src/main/java/java/util/Random.java
@@ -56,15 +56,19 @@ public class Random implements Serializable {
private double nextNextGaussian;
/**
+ * Used to generate initial seeds.
+ */
+ private static volatile long seedBase = 0;
+
+ /**
* Constructs a random generator with an initial state that is
* unlikely to be duplicated by a subsequent instantiation.
- *
- * <p>The initial state (that is, the seed) is <i>partially</i> based
- * on the current time of day in milliseconds.
*/
public Random() {
- // Note: Using identityHashCode() to be hermetic wrt subclasses.
- setSeed(System.currentTimeMillis() + System.identityHashCode(this));
+ // Note: Don't use identityHashCode(this) since that causes the monitor to
+ // get inflated when we synchronize.
+ setSeed(System.nanoTime() + seedBase);
+ ++seedBase;
}
/**
@@ -85,6 +89,9 @@ public class Random implements Serializable {
* Volume 2: Seminumerical Algorithms</i>, section 3.2.1.
*
* <p>Most applications will want to use one of this class' convenience methods instead.
+ *
+ * <p>Subclasses only need to override this method to alter the behavior
+ * of all the public methods.
*/
protected synchronized int next(int bits) {
seed = (seed * multiplier + 0xbL) & ((1L << 48) - 1);
diff --git a/luni/src/main/java/java/util/Scanner.java b/luni/src/main/java/java/util/Scanner.java
index 7d504b7..7d0e795 100644
--- a/luni/src/main/java/java/util/Scanner.java
+++ b/luni/src/main/java/java/util/Scanner.java
@@ -159,12 +159,15 @@ public final class Scanner implements Closeable, Iterator<String> {
if (charsetName == null) {
throw new IllegalArgumentException("charsetName == null");
}
+
+ InputStreamReader streamReader;
try {
- setInput(new InputStreamReader(fis, charsetName));
+ streamReader = new InputStreamReader(fis, charsetName);
} catch (UnsupportedEncodingException e) {
IoUtils.closeQuietly(fis);
throw new IllegalArgumentException(e.getMessage());
}
+ initialize(streamReader);
}
/**
@@ -174,7 +177,7 @@ public final class Scanner implements Closeable, Iterator<String> {
* the string to be scanned.
*/
public Scanner(String src) {
- setInput(new StringReader(src));
+ initialize(new StringReader(src));
}
/**
@@ -203,11 +206,14 @@ public final class Scanner implements Closeable, Iterator<String> {
if (src == null) {
throw new NullPointerException("src == null");
}
+
+ InputStreamReader streamReader;
try {
- setInput(new InputStreamReader(src, charsetName));
+ streamReader = new InputStreamReader(src, charsetName);
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e.getMessage());
}
+ initialize(streamReader);
}
/**
@@ -220,7 +226,7 @@ public final class Scanner implements Closeable, Iterator<String> {
if (src == null) {
throw new NullPointerException("src == null");
}
- setInput(src);
+ initialize(src);
}
/**
@@ -252,13 +258,14 @@ public final class Scanner implements Closeable, Iterator<String> {
if (charsetName == null) {
throw new IllegalArgumentException("charsetName == null");
}
- setInput(Channels.newReader(src, charsetName));
+ initialize(Channels.newReader(src, charsetName));
}
- private void setInput(Readable input) {
+ private void initialize(Readable input) {
this.input = input;
- buffer.limit(0);
- matcher = delimiter.matcher(buffer);
+ matcher = delimiter.matcher("");
+ matcher.useTransparentBounds(true);
+ matcher.useAnchoringBounds(false);
}
/**
@@ -535,7 +542,7 @@ public final class Scanner implements Closeable, Iterator<String> {
checkOpen();
checkNotNull(pattern);
matchSuccessful = false;
- saveCurrentStatus();
+ prepareForScan();
// if the next token exists, set the match region, otherwise return
// false
if (!setTokenRegion()) {
@@ -790,7 +797,7 @@ public final class Scanner implements Closeable, Iterator<String> {
* @throws IllegalStateException if this {@code Scanner} is closed.
*/
public boolean hasNextLine() {
- saveCurrentStatus();
+ prepareForScan();
String result = findWithinHorizon(LINE_PATTERN, 0);
recoverPreviousStatus();
return result != null;
@@ -954,7 +961,7 @@ public final class Scanner implements Closeable, Iterator<String> {
checkOpen();
checkNotNull(pattern);
matchSuccessful = false;
- saveCurrentStatus();
+ prepareForScan();
if (!setTokenRegion()) {
recoverPreviousStatus();
// if setting match region fails
@@ -1204,7 +1211,7 @@ public final class Scanner implements Closeable, Iterator<String> {
Pattern floatPattern = getFloatPattern();
String floatString = next(floatPattern);
floatString = removeLocaleInfoFromFloat(floatString);
- double doubleValue = 0;
+ double doubleValue;
try {
doubleValue = Double.parseDouble(floatString);
} catch (NumberFormatException e) {
@@ -1248,7 +1255,7 @@ public final class Scanner implements Closeable, Iterator<String> {
Pattern floatPattern = getFloatPattern();
String floatString = next(floatPattern);
floatString = removeLocaleInfoFromFloat(floatString);
- float floatValue = 0;
+ float floatValue;
try {
floatValue = Float.parseFloat(floatString);
} catch (NumberFormatException e) {
@@ -1310,7 +1317,7 @@ public final class Scanner implements Closeable, Iterator<String> {
Pattern integerPattern = getIntegerPattern(radix);
String intString = next(integerPattern);
intString = removeLocaleInfo(intString, int.class);
- int intValue = 0;
+ int intValue;
try {
intValue = Integer.parseInt(intString, radix);
} catch (NumberFormatException e) {
@@ -1340,7 +1347,7 @@ public final class Scanner implements Closeable, Iterator<String> {
matcher.usePattern(LINE_PATTERN);
matcher.region(findStartIndex, bufferLength);
- String result = null;
+ String result;
while (true) {
if (matcher.find()) {
if (inputExhausted || matcher.end() != bufferLength
@@ -1422,7 +1429,7 @@ public final class Scanner implements Closeable, Iterator<String> {
Pattern integerPattern = getIntegerPattern(radix);
String intString = next(integerPattern);
intString = removeLocaleInfo(intString, int.class);
- long longValue = 0;
+ long longValue;
try {
longValue = Long.parseLong(intString, radix);
} catch (NumberFormatException e) {
@@ -1484,7 +1491,7 @@ public final class Scanner implements Closeable, Iterator<String> {
Pattern integerPattern = getIntegerPattern(radix);
String intString = next(integerPattern);
intString = removeLocaleInfo(intString, int.class);
- short shortValue = 0;
+ short shortValue;
try {
shortValue = Short.parseShort(intString, radix);
} catch (NumberFormatException e) {
@@ -1662,23 +1669,46 @@ public final class Scanner implements Closeable, Iterator<String> {
}
/*
- * Change the matcher's string after reading input
+ * Change the matcher's input after modifying the contents of the buffer.
+ * The current implementation of Matcher causes a copy of the buffer to be taken.
*/
private void resetMatcher() {
- if (matcher == null) {
- matcher = delimiter.matcher(buffer);
- } else {
- matcher.reset(buffer);
- }
- matcher.useTransparentBounds(true);
- matcher.useAnchoringBounds(false);
+ matcher.reset(buffer);
matcher.region(findStartIndex, bufferLength);
}
/*
- * Save the matcher's last find position
- */
- private void saveCurrentStatus() {
+ * Recover buffer space for characters that are already processed and save the matcher's state
+ * in case parsing fails. See recoverPrevousState. This method must be called before
+ * any buffer offsets are calculated.
+ */
+ private void prepareForScan() {
+ // Compacting the buffer recovers space taken by already processed characters. This does not
+ // prevent the buffer growing in all situations but keeps the buffer small when delimiters
+ // exist regularly.
+ if (findStartIndex >= buffer.capacity() / 2) {
+ // When over half the buffer is filled with characters no longer being considered by the
+ // scanner we take the cost of compacting the buffer.
+
+ // Move all characters from [findStartIndex, findStartIndex + remaining()) to
+ // [0, remaining()).
+ int oldPosition = buffer.position();
+ buffer.position(findStartIndex);
+ buffer.compact();
+ buffer.position(oldPosition);
+
+ // Update Scanner state to reflect the new buffer state.
+ bufferLength -= findStartIndex;
+ findStartIndex = 0;
+ preStartIndex = -1;
+
+ // The matcher must also be informed that the buffer has changed because it operates on
+ // a String copy.
+ resetMatcher();
+ }
+
+ // Save the matcher's last find position so it can be returned to if the next token cannot
+ // be parsed.
preStartIndex = findStartIndex;
}
@@ -1822,7 +1852,7 @@ public final class Scanner implements Closeable, Iterator<String> {
boolean negative = removeLocaleSign(tokenBuilder);
// Remove group separator
String groupSeparator = String.valueOf(dfs.getGroupingSeparator());
- int separatorIndex = -1;
+ int separatorIndex;
while ((separatorIndex = tokenBuilder.indexOf(groupSeparator)) != -1) {
tokenBuilder.delete(separatorIndex, separatorIndex + 1);
}
@@ -1909,9 +1939,9 @@ public final class Scanner implements Closeable, Iterator<String> {
*/
private boolean setTokenRegion() {
// The position where token begins
- int tokenStartIndex = 0;
+ int tokenStartIndex;
// The position where token ends
- int tokenEndIndex = 0;
+ int tokenEndIndex;
// Use delimiter pattern
matcher.usePattern(delimiter);
matcher.region(findStartIndex, bufferLength);
@@ -1945,8 +1975,7 @@ public final class Scanner implements Closeable, Iterator<String> {
if (matcher.find()) {
findComplete = true;
// If just delimiter remains
- if (matcher.start() == findStartIndex
- && matcher.end() == bufferLength) {
+ if (matcher.start() == findStartIndex && matcher.end() == bufferLength) {
// If more input resource exists
if (!inputExhausted) {
readMore();
@@ -1964,7 +1993,7 @@ public final class Scanner implements Closeable, Iterator<String> {
}
}
tokenStartIndex = matcher.end();
- findStartIndex = matcher.end();
+ findStartIndex = tokenStartIndex;
return tokenStartIndex;
}
@@ -1984,7 +2013,7 @@ public final class Scanner implements Closeable, Iterator<String> {
setSuccess = true;
}
// If the first delimiter of scanner is not at the find start position
- if (-1 != findIndex && preStartIndex != matcher.start()) {
+ if (findIndex != -1 && preStartIndex != matcher.start()) {
tokenStartIndex = preStartIndex;
tokenEndIndex = matcher.start();
findStartIndex = matcher.start();
@@ -1996,7 +2025,7 @@ public final class Scanner implements Closeable, Iterator<String> {
}
private int findDelimiterAfter() {
- int tokenEndIndex = 0;
+ int tokenEndIndex;
boolean findComplete = false;
while (!findComplete) {
if (matcher.find()) {
@@ -2014,7 +2043,7 @@ public final class Scanner implements Closeable, Iterator<String> {
}
}
tokenEndIndex = matcher.start();
- findStartIndex = matcher.start();
+ findStartIndex = tokenEndIndex;
return tokenEndIndex;
}
@@ -2032,7 +2061,7 @@ public final class Scanner implements Closeable, Iterator<String> {
}
// Read input resource
- int readCount = 0;
+ int readCount;
try {
buffer.limit(buffer.capacity());
buffer.position(oldBufferLength);
diff --git a/luni/src/main/java/java/util/TimeZone.java b/luni/src/main/java/java/util/TimeZone.java
index c024e8d..854a4a6 100644
--- a/luni/src/main/java/java/util/TimeZone.java
+++ b/luni/src/main/java/java/util/TimeZone.java
@@ -63,7 +63,7 @@ import org.apache.harmony.luni.internal.util.TimezoneGetter;
*
* @see Calendar
* @see GregorianCalendar
- * @see SimpleDateFormat
+ * @see java.text.SimpleDateFormat
*/
public abstract class TimeZone implements Serializable, Cloneable {
private static final long serialVersionUID = 3581463369166924961L;
@@ -206,27 +206,48 @@ public abstract class TimeZone implements Serializable, Cloneable {
// upgrade to icu4c 50 and rewrite the underlying native code. See also the
// "element[j] != null" check in SimpleDateFormat.parseTimeZone, and the extra work in
// DateFormatSymbols.getZoneStrings.
-
- int offset = getRawOffset();
+ int offsetMillis = getRawOffset();
if (daylightTime) {
- offset += getDSTSavings();
+ offsetMillis += getDSTSavings();
}
- offset /= 60000;
+ return createGmtOffsetString(true /* includeGmt */, true /* includeMinuteSeparator */,
+ offsetMillis);
+ }
+
+ /**
+ * Returns a string representation of an offset from UTC.
+ *
+ * <p>The format is "[GMT](+|-)HH[:]MM". The output is not localized.
+ *
+ * @param includeGmt true to include "GMT", false to exclude
+ * @param includeMinuteSeparator true to include the separator between hours and minutes, false
+ * to exclude.
+ * @param offsetMillis the offset from UTC
+ *
+ * @hide used internally by SimpleDateFormat
+ */
+ public static String createGmtOffsetString(boolean includeGmt,
+ boolean includeMinuteSeparator, int offsetMillis) {
+ int offsetMinutes = offsetMillis / 60000;
char sign = '+';
- if (offset < 0) {
+ if (offsetMinutes < 0) {
sign = '-';
- offset = -offset;
+ offsetMinutes = -offsetMinutes;
}
StringBuilder builder = new StringBuilder(9);
- builder.append("GMT");
+ if (includeGmt) {
+ builder.append("GMT");
+ }
builder.append(sign);
- appendNumber(builder, 2, offset / 60);
- builder.append(':');
- appendNumber(builder, 2, offset % 60);
+ appendNumber(builder, 2, offsetMinutes / 60);
+ if (includeMinuteSeparator) {
+ builder.append(':');
+ }
+ appendNumber(builder, 2, offsetMinutes % 60);
return builder.toString();
}
- private void appendNumber(StringBuilder builder, int count, int value) {
+ private static void appendNumber(StringBuilder builder, int count, int value) {
String string = Integer.toString(value);
for (int i = 0; i < count - string.length(); i++) {
builder.append('0');
@@ -329,7 +350,6 @@ public abstract class TimeZone implements Serializable, Cloneable {
}
// Special cases? These can clone an existing instance.
- // TODO: should we just add a cache to ZoneInfoDB instead?
if (id.length() == 3) {
if (id.equals("GMT")) {
return (TimeZone) GMT.clone();
diff --git a/luni/src/main/java/java/util/Timer.java b/luni/src/main/java/java/util/Timer.java
index 25ac432..7192f9b 100644
--- a/luni/src/main/java/java/util/Timer.java
+++ b/luni/src/main/java/java/util/Timer.java
@@ -356,9 +356,7 @@ public class Timer {
* Creates a new named {@code Timer} which may be specified to be run as a
* daemon thread.
*
- * @param name the name of the {@code Timer}.
- * @param isDaemon true if {@code Timer}'s thread should be a daemon thread.
- * @throws NullPointerException is {@code name} is {@code null}
+ * @throws NullPointerException if {@code name == null}
*/
public Timer(String name, boolean isDaemon) {
if (name == null) {
@@ -371,8 +369,7 @@ public class Timer {
/**
* Creates a new named {@code Timer} which does not run as a daemon thread.
*
- * @param name the name of the Timer.
- * @throws NullPointerException is {@code name} is {@code null}
+ * @throws NullPointerException if {@code name == null}
*/
public Timer(String name) {
this(name, false);
diff --git a/luni/src/main/java/java/util/TreeSet.java b/luni/src/main/java/java/util/TreeSet.java
index 502329e..791ffa6 100644
--- a/luni/src/main/java/java/util/TreeSet.java
+++ b/luni/src/main/java/java/util/TreeSet.java
@@ -258,7 +258,7 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>,
/**
* Returns the first element in this set.
- * @exception NoSuchElementException when this TreeSet is empty
+ * @throws NoSuchElementException when this TreeSet is empty
*/
public E first() {
return backingMap.firstKey();
@@ -266,7 +266,7 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>,
/**
* Returns the last element in this set.
- * @exception NoSuchElementException when this TreeSet is empty
+ * @throws NoSuchElementException when this TreeSet is empty
*/
public E last() {
return backingMap.lastKey();
@@ -413,10 +413,10 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>,
* @return a subset where the elements are greater or equal to
* <code>start</code> and less than <code>end</code>
*
- * @exception ClassCastException
+ * @throws ClassCastException
* when the start or end object cannot be compared with the
* elements in this TreeSet
- * @exception NullPointerException
+ * @throws NullPointerException
* when the start or end object is null and the comparator
* cannot handle null
*/
@@ -434,10 +434,10 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>,
* the end element
* @return a subset where the elements are less than <code>end</code>
*
- * @exception ClassCastException
+ * @throws ClassCastException
* when the end object cannot be compared with the elements
* in this TreeSet
- * @exception NullPointerException
+ * @throws NullPointerException
* when the end object is null and the comparator cannot
* handle null
*/
@@ -457,10 +457,10 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>,
* @return a subset where the elements are greater or equal to
* <code>start</code>
*
- * @exception ClassCastException
+ * @throws ClassCastException
* when the start object cannot be compared with the elements
* in this TreeSet
- * @exception NullPointerException
+ * @throws NullPointerException
* when the start object is null and the comparator cannot
* handle null
*/
diff --git a/luni/src/main/java/java/util/UUID.java b/luni/src/main/java/java/util/UUID.java
index 3594d87..020ac95 100644
--- a/luni/src/main/java/java/util/UUID.java
+++ b/luni/src/main/java/java/util/UUID.java
@@ -182,28 +182,17 @@ public final class UUID implements Serializable, Comparable<UUID> {
throw new NullPointerException("uuid == null");
}
- int[] position = new int[5];
- int lastPosition = 1;
- int startPosition = 0;
-
- int i = 0;
- for (; i < position.length && lastPosition > 0; i++) {
- position[i] = uuid.indexOf("-", startPosition);
- lastPosition = position[i];
- startPosition = position[i] + 1;
- }
-
- // should have and only can have four "-" in UUID
- if (i != position.length || lastPosition != -1) {
+ String[] parts = uuid.split("-");
+ if (parts.length != 5) {
throw new IllegalArgumentException("Invalid UUID: " + uuid);
}
- long m1 = Long.parseLong(uuid.substring(0, position[0]), 16);
- long m2 = Long.parseLong(uuid.substring(position[0] + 1, position[1]), 16);
- long m3 = Long.parseLong(uuid.substring(position[1] + 1, position[2]), 16);
+ long m1 = Long.parsePositiveLong(parts[0], 16);
+ long m2 = Long.parsePositiveLong(parts[1], 16);
+ long m3 = Long.parsePositiveLong(parts[2], 16);
- long lsb1 = Long.parseLong(uuid.substring(position[2] + 1, position[3]), 16);
- long lsb2 = Long.parseLong(uuid.substring(position[3] + 1), 16);
+ long lsb1 = Long.parsePositiveLong(parts[3], 16);
+ long lsb2 = Long.parsePositiveLong(parts[4], 16);
long msb = (m1 << 32) | (m2 << 16) | m3;
long lsb = (lsb1 << 48) | lsb2;
diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java b/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java
index c85a5cc..ea3b1e9 100644
--- a/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java
+++ b/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java
@@ -5,865 +5,729 @@
*/
package java.util.concurrent;
-import java.util.concurrent.locks.*;
-import java.util.*;
+
+import java.io.ObjectStreamField;
import java.io.Serializable;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.ConcurrentModificationException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.LockSupport;
+import java.util.concurrent.locks.ReentrantLock;
// BEGIN android-note
// removed link to collections framework docs
+// removed links to hidden api
// END android-note
/**
* A hash table supporting full concurrency of retrievals and
- * adjustable expected concurrency for updates. This class obeys the
+ * high expected concurrency for updates. This class obeys the
* same functional specification as {@link java.util.Hashtable}, and
* includes versions of methods corresponding to each method of
- * <tt>Hashtable</tt>. However, even though all operations are
+ * {@code Hashtable}. However, even though all operations are
* thread-safe, retrieval operations do <em>not</em> entail locking,
* and there is <em>not</em> any support for locking the entire table
* in a way that prevents all access. This class is fully
- * interoperable with <tt>Hashtable</tt> in programs that rely on its
+ * interoperable with {@code Hashtable} in programs that rely on its
* thread safety but not on its synchronization details.
*
- * <p> Retrieval operations (including <tt>get</tt>) generally do not
- * block, so may overlap with update operations (including
- * <tt>put</tt> and <tt>remove</tt>). Retrievals reflect the results
- * of the most recently <em>completed</em> update operations holding
- * upon their onset. For aggregate operations such as <tt>putAll</tt>
- * and <tt>clear</tt>, concurrent retrievals may reflect insertion or
- * removal of only some entries. Similarly, Iterators and
- * Enumerations return elements reflecting the state of the hash table
- * at some point at or since the creation of the iterator/enumeration.
- * They do <em>not</em> throw {@link ConcurrentModificationException}.
- * However, iterators are designed to be used by only one thread at a time.
+ * <p>Retrieval operations (including {@code get}) generally do not
+ * block, so may overlap with update operations (including {@code put}
+ * and {@code remove}). Retrievals reflect the results of the most
+ * recently <em>completed</em> update operations holding upon their
+ * onset. (More formally, an update operation for a given key bears a
+ * <em>happens-before</em> relation with any (non-null) retrieval for
+ * that key reporting the updated value.) For aggregate operations
+ * such as {@code putAll} and {@code clear}, concurrent retrievals may
+ * reflect insertion or removal of only some entries. Similarly,
+ * Iterators and Enumerations return elements reflecting the state of
+ * the hash table at some point at or since the creation of the
+ * iterator/enumeration. They do <em>not</em> throw {@link
+ * ConcurrentModificationException}. However, iterators are designed
+ * to be used by only one thread at a time. Bear in mind that the
+ * results of aggregate status methods including {@code size}, {@code
+ * isEmpty}, and {@code containsValue} are typically useful only when
+ * a map is not undergoing concurrent updates in other threads.
+ * Otherwise the results of these methods reflect transient states
+ * that may be adequate for monitoring or estimation purposes, but not
+ * for program control.
*
- * <p> The allowed concurrency among update operations is guided by
- * the optional <tt>concurrencyLevel</tt> constructor argument
- * (default <tt>16</tt>), which is used as a hint for internal sizing. The
- * table is internally partitioned to try to permit the indicated
- * number of concurrent updates without contention. Because placement
- * in hash tables is essentially random, the actual concurrency will
- * vary. Ideally, you should choose a value to accommodate as many
- * threads as will ever concurrently modify the table. Using a
- * significantly higher value than you need can waste space and time,
- * and a significantly lower value can lead to thread contention. But
- * overestimates and underestimates within an order of magnitude do
- * not usually have much noticeable impact. A value of one is
- * appropriate when it is known that only one thread will modify and
- * all others will only read. Also, resizing this or any other kind of
- * hash table is a relatively slow operation, so, when possible, it is
- * a good idea to provide estimates of expected table sizes in
- * constructors.
+ * <p>The table is dynamically expanded when there are too many
+ * collisions (i.e., keys that have distinct hash codes but fall into
+ * the same slot modulo the table size), with the expected average
+ * effect of maintaining roughly two bins per mapping (corresponding
+ * to a 0.75 load factor threshold for resizing). There may be much
+ * variance around this average as mappings are added and removed, but
+ * overall, this maintains a commonly accepted time/space tradeoff for
+ * hash tables. However, resizing this or any other kind of hash
+ * table may be a relatively slow operation. When possible, it is a
+ * good idea to provide a size estimate as an optional {@code
+ * initialCapacity} constructor argument. An additional optional
+ * {@code loadFactor} constructor argument provides a further means of
+ * customizing initial table capacity by specifying the table density
+ * to be used in calculating the amount of space to allocate for the
+ * given number of elements. Also, for compatibility with previous
+ * versions of this class, constructors may optionally specify an
+ * expected {@code concurrencyLevel} as an additional hint for
+ * internal sizing. Note that using many keys with exactly the same
+ * {@code hashCode()} is a sure way to slow down performance of any
+ * hash table. To ameliorate impact, when keys are {@link Comparable},
+ * this class may use comparison order among keys to help break ties.
*
* <p>This class and its views and iterators implement all of the
* <em>optional</em> methods of the {@link Map} and {@link Iterator}
* interfaces.
*
- * <p> Like {@link Hashtable} but unlike {@link HashMap}, this class
- * does <em>not</em> allow <tt>null</tt> to be used as a key or value.
+ * <p>Like {@link Hashtable} but unlike {@link HashMap}, this class
+ * does <em>not</em> allow {@code null} to be used as a key or value.
*
* @since 1.5
* @author Doug Lea
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
-public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
- implements ConcurrentMap<K, V>, Serializable {
+public class ConcurrentHashMap<K,V> extends java.util.AbstractMap<K,V>
+ implements ConcurrentMap<K,V>, Serializable {
private static final long serialVersionUID = 7249069246763182397L;
/*
- * The basic strategy is to subdivide the table among Segments,
- * each of which itself is a concurrently readable hash table. To
- * reduce footprint, all but one segments are constructed only
- * when first needed (see ensureSegment). To maintain visibility
- * in the presence of lazy construction, accesses to segments as
- * well as elements of segment's table must use volatile access,
- * which is done via Unsafe within methods segmentAt etc
- * below. These provide the functionality of AtomicReferenceArrays
- * but reduce the levels of indirection. Additionally,
- * volatile-writes of table elements and entry "next" fields
- * within locked operations use the cheaper "lazySet" forms of
- * writes (via putOrderedObject) because these writes are always
- * followed by lock releases that maintain sequential consistency
- * of table updates.
- *
- * Historical note: The previous version of this class relied
- * heavily on "final" fields, which avoided some volatile reads at
- * the expense of a large initial footprint. Some remnants of
- * that design (including forced construction of segment 0) exist
- * to ensure serialization compatibility.
+ * Overview:
+ *
+ * The primary design goal of this hash table is to maintain
+ * concurrent readability (typically method get(), but also
+ * iterators and related methods) while minimizing update
+ * contention. Secondary goals are to keep space consumption about
+ * the same or better than java.util.HashMap, and to support high
+ * initial insertion rates on an empty table by many threads.
+ *
+ * This map usually acts as a binned (bucketed) hash table. Each
+ * key-value mapping is held in a Node. Most nodes are instances
+ * of the basic Node class with hash, key, value, and next
+ * fields. However, various subclasses exist: TreeNodes are
+ * arranged in balanced trees, not lists. TreeBins hold the roots
+ * of sets of TreeNodes. ForwardingNodes are placed at the heads
+ * of bins during resizing. ReservationNodes are used as
+ * placeholders while establishing values in computeIfAbsent and
+ * related methods. The types TreeBin, ForwardingNode, and
+ * ReservationNode do not hold normal user keys, values, or
+ * hashes, and are readily distinguishable during search etc
+ * because they have negative hash fields and null key and value
+ * fields. (These special nodes are either uncommon or transient,
+ * so the impact of carrying around some unused fields is
+ * insignificant.)
+ *
+ * The table is lazily initialized to a power-of-two size upon the
+ * first insertion. Each bin in the table normally contains a
+ * list of Nodes (most often, the list has only zero or one Node).
+ * Table accesses require volatile/atomic reads, writes, and
+ * CASes. Because there is no other way to arrange this without
+ * adding further indirections, we use intrinsics
+ * (sun.misc.Unsafe) operations.
+ *
+ * We use the top (sign) bit of Node hash fields for control
+ * purposes -- it is available anyway because of addressing
+ * constraints. Nodes with negative hash fields are specially
+ * handled or ignored in map methods.
+ *
+ * Insertion (via put or its variants) of the first node in an
+ * empty bin is performed by just CASing it to the bin. This is
+ * by far the most common case for put operations under most
+ * key/hash distributions. Other update operations (insert,
+ * delete, and replace) require locks. We do not want to waste
+ * the space required to associate a distinct lock object with
+ * each bin, so instead use the first node of a bin list itself as
+ * a lock. Locking support for these locks relies on builtin
+ * "synchronized" monitors.
+ *
+ * Using the first node of a list as a lock does not by itself
+ * suffice though: When a node is locked, any update must first
+ * validate that it is still the first node after locking it, and
+ * retry if not. Because new nodes are always appended to lists,
+ * once a node is first in a bin, it remains first until deleted
+ * or the bin becomes invalidated (upon resizing).
+ *
+ * The main disadvantage of per-bin locks is that other update
+ * operations on other nodes in a bin list protected by the same
+ * lock can stall, for example when user equals() or mapping
+ * functions take a long time. However, statistically, under
+ * random hash codes, this is not a common problem. Ideally, the
+ * frequency of nodes in bins follows a Poisson distribution
+ * (http://en.wikipedia.org/wiki/Poisson_distribution) with a
+ * parameter of about 0.5 on average, given the resizing threshold
+ * of 0.75, although with a large variance because of resizing
+ * granularity. Ignoring variance, the expected occurrences of
+ * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The
+ * first values are:
+ *
+ * 0: 0.60653066
+ * 1: 0.30326533
+ * 2: 0.07581633
+ * 3: 0.01263606
+ * 4: 0.00157952
+ * 5: 0.00015795
+ * 6: 0.00001316
+ * 7: 0.00000094
+ * 8: 0.00000006
+ * more: less than 1 in ten million
+ *
+ * Lock contention probability for two threads accessing distinct
+ * elements is roughly 1 / (8 * #elements) under random hashes.
+ *
+ * Actual hash code distributions encountered in practice
+ * sometimes deviate significantly from uniform randomness. This
+ * includes the case when N > (1<<30), so some keys MUST collide.
+ * Similarly for dumb or hostile usages in which multiple keys are
+ * designed to have identical hash codes or ones that differs only
+ * in masked-out high bits. So we use a secondary strategy that
+ * applies when the number of nodes in a bin exceeds a
+ * threshold. These TreeBins use a balanced tree to hold nodes (a
+ * specialized form of red-black trees), bounding search time to
+ * O(log N). Each search step in a TreeBin is at least twice as
+ * slow as in a regular list, but given that N cannot exceed
+ * (1<<64) (before running out of addresses) this bounds search
+ * steps, lock hold times, etc, to reasonable constants (roughly
+ * 100 nodes inspected per operation worst case) so long as keys
+ * are Comparable (which is very common -- String, Long, etc).
+ * TreeBin nodes (TreeNodes) also maintain the same "next"
+ * traversal pointers as regular nodes, so can be traversed in
+ * iterators in the same way.
+ *
+ * The table is resized when occupancy exceeds a percentage
+ * threshold (nominally, 0.75, but see below). Any thread
+ * noticing an overfull bin may assist in resizing after the
+ * initiating thread allocates and sets up the replacement
+ * array. However, rather than stalling, these other threads may
+ * proceed with insertions etc. The use of TreeBins shields us
+ * from the worst case effects of overfilling while resizes are in
+ * progress. Resizing proceeds by transferring bins, one by one,
+ * from the table to the next table. To enable concurrency, the
+ * next table must be (incrementally) prefilled with place-holders
+ * serving as reverse forwarders to the old table. Because we are
+ * using power-of-two expansion, the elements from each bin must
+ * either stay at same index, or move with a power of two
+ * offset. We eliminate unnecessary node creation by catching
+ * cases where old nodes can be reused because their next fields
+ * won't change. On average, only about one-sixth of them need
+ * cloning when a table doubles. The nodes they replace will be
+ * garbage collectable as soon as they are no longer referenced by
+ * any reader thread that may be in the midst of concurrently
+ * traversing table. Upon transfer, the old table bin contains
+ * only a special forwarding node (with hash field "MOVED") that
+ * contains the next table as its key. On encountering a
+ * forwarding node, access and update operations restart, using
+ * the new table.
+ *
+ * Each bin transfer requires its bin lock, which can stall
+ * waiting for locks while resizing. However, because other
+ * threads can join in and help resize rather than contend for
+ * locks, average aggregate waits become shorter as resizing
+ * progresses. The transfer operation must also ensure that all
+ * accessible bins in both the old and new table are usable by any
+ * traversal. This is arranged by proceeding from the last bin
+ * (table.length - 1) up towards the first. Upon seeing a
+ * forwarding node, traversals (see class Traverser) arrange to
+ * move to the new table without revisiting nodes. However, to
+ * ensure that no intervening nodes are skipped, bin splitting can
+ * only begin after the associated reverse-forwarders are in
+ * place.
+ *
+ * The traversal scheme also applies to partial traversals of
+ * ranges of bins (via an alternate Traverser constructor)
+ * to support partitioned aggregate operations. Also, read-only
+ * operations give up if ever forwarded to a null table, which
+ * provides support for shutdown-style clearing, which is also not
+ * currently implemented.
+ *
+ * Lazy table initialization minimizes footprint until first use,
+ * and also avoids resizings when the first operation is from a
+ * putAll, constructor with map argument, or deserialization.
+ * These cases attempt to override the initial capacity settings,
+ * but harmlessly fail to take effect in cases of races.
+ *
+ * The element count is maintained using a specialization of
+ * LongAdder. We need to incorporate a specialization rather than
+ * just use a LongAdder in order to access implicit
+ * contention-sensing that leads to creation of multiple
+ * CounterCells. The counter mechanics avoid contention on
+ * updates but can encounter cache thrashing if read too
+ * frequently during concurrent access. To avoid reading so often,
+ * resizing under contention is attempted only upon adding to a
+ * bin already holding two or more nodes. Under uniform hash
+ * distributions, the probability of this occurring at threshold
+ * is around 13%, meaning that only about 1 in 8 puts check
+ * threshold (and after resizing, many fewer do so).
+ *
+ * TreeBins use a special form of comparison for search and
+ * related operations (which is the main reason we cannot use
+ * existing collections such as TreeMaps). TreeBins contain
+ * Comparable elements, but may contain others, as well as
+ * elements that are Comparable but not necessarily Comparable
+ * for the same T, so we cannot invoke compareTo among them. To
+ * handle this, the tree is ordered primarily by hash value, then
+ * by Comparable.compareTo order if applicable. On lookup at a
+ * node, if elements are not comparable or compare as 0 then both
+ * left and right children may need to be searched in the case of
+ * tied hash values. (This corresponds to the full list search
+ * that would be necessary if all elements were non-Comparable and
+ * had tied hashes.) The red-black balancing code is updated from
+ * pre-jdk-collections
+ * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java)
+ * based in turn on Cormen, Leiserson, and Rivest "Introduction to
+ * Algorithms" (CLR).
+ *
+ * TreeBins also require an additional locking mechanism. While
+ * list traversal is always possible by readers even during
+ * updates, tree traversal is not, mainly because of tree-rotations
+ * that may change the root node and/or its linkages. TreeBins
+ * include a simple read-write lock mechanism parasitic on the
+ * main bin-synchronization strategy: Structural adjustments
+ * associated with an insertion or removal are already bin-locked
+ * (and so cannot conflict with other writers) but must wait for
+ * ongoing readers to finish. Since there can be only one such
+ * waiter, we use a simple scheme using a single "waiter" field to
+ * block writers. However, readers need never block. If the root
+ * lock is held, they proceed along the slow traversal path (via
+ * next-pointers) until the lock becomes available or the list is
+ * exhausted, whichever comes first. These cases are not fast, but
+ * maximize aggregate expected throughput.
+ *
+ * Maintaining API and serialization compatibility with previous
+ * versions of this class introduces several oddities. Mainly: We
+ * leave untouched but unused constructor arguments refering to
+ * concurrencyLevel. We accept a loadFactor constructor argument,
+ * but apply it only to initial table capacity (which is the only
+ * time that we can guarantee to honor it.) We also declare an
+ * unused "Segment" class that is instantiated in minimal form
+ * only when serializing.
+ *
+ * This file is organized to make things a little easier to follow
+ * while reading than they might otherwise: First the main static
+ * declarations and utilities, then fields, then main public
+ * methods (with a few factorings of multiple public methods into
+ * internal ones), then sizing methods, trees, traversers, and
+ * bulk operations.
*/
/* ---------------- Constants -------------- */
/**
- * The default initial capacity for this table,
- * used when not otherwise specified in a constructor.
+ * The largest possible table capacity. This value must be
+ * exactly 1<<30 to stay within Java array allocation and indexing
+ * bounds for power of two table sizes, and is further required
+ * because the top two bits of 32bit hash fields are used for
+ * control purposes.
*/
- static final int DEFAULT_INITIAL_CAPACITY = 16;
+ private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
- * The default load factor for this table, used when not
- * otherwise specified in a constructor.
+ * The default initial table capacity. Must be a power of 2
+ * (i.e., at least 1) and at most MAXIMUM_CAPACITY.
*/
- static final float DEFAULT_LOAD_FACTOR = 0.75f;
+ private static final int DEFAULT_CAPACITY = 16;
/**
- * The default concurrency level for this table, used when not
- * otherwise specified in a constructor.
+ * The largest possible (non-power of two) array size.
+ * Needed by toArray and related methods.
*/
- static final int DEFAULT_CONCURRENCY_LEVEL = 16;
+ static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
- * The maximum capacity, used if a higher value is implicitly
- * specified by either of the constructors with arguments. MUST
- * be a power of two <= 1<<30 to ensure that entries are indexable
- * using ints.
+ * The default concurrency level for this table. Unused but
+ * defined for compatibility with previous versions of this class.
*/
- static final int MAXIMUM_CAPACITY = 1 << 30;
+ private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/**
- * The minimum capacity for per-segment tables. Must be a power
- * of two, at least two to avoid immediate resizing on next use
- * after lazy construction.
+ * The load factor for this table. Overrides of this value in
+ * constructors affect only the initial table capacity. The
+ * actual floating point value isn't normally used -- it is
+ * simpler to use expressions such as {@code n - (n >>> 2)} for
+ * the associated resizing threshold.
*/
- static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
+ private static final float LOAD_FACTOR = 0.75f;
/**
- * The maximum number of segments to allow; used to bound
- * constructor arguments. Must be power of two less than 1 << 24.
+ * The bin count threshold for using a tree rather than list for a
+ * bin. Bins are converted to trees when adding an element to a
+ * bin with at least this many nodes. The value must be greater
+ * than 2, and should be at least 8 to mesh with assumptions in
+ * tree removal about conversion back to plain bins upon
+ * shrinkage.
*/
- static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
+ static final int TREEIFY_THRESHOLD = 8;
/**
- * Number of unsynchronized retries in size and containsValue
- * methods before resorting to locking. This is used to avoid
- * unbounded retries if tables undergo continuous modification
- * which would make it impossible to obtain an accurate result.
+ * The bin count threshold for untreeifying a (split) bin during a
+ * resize operation. Should be less than TREEIFY_THRESHOLD, and at
+ * most 6 to mesh with shrinkage detection under removal.
*/
- static final int RETRIES_BEFORE_LOCK = 2;
-
- /* ---------------- Fields -------------- */
+ static final int UNTREEIFY_THRESHOLD = 6;
/**
- * Mask value for indexing into segments. The upper bits of a
- * key's hash code are used to choose the segment.
+ * The smallest table capacity for which bins may be treeified.
+ * (Otherwise the table is resized if too many nodes in a bin.)
+ * The value should be at least 4 * TREEIFY_THRESHOLD to avoid
+ * conflicts between resizing and treeification thresholds.
*/
- final int segmentMask;
+ static final int MIN_TREEIFY_CAPACITY = 64;
/**
- * Shift value for indexing within segments.
+ * Minimum number of rebinnings per transfer step. Ranges are
+ * subdivided to allow multiple resizer threads. This value
+ * serves as a lower bound to avoid resizers encountering
+ * excessive memory contention. The value should be at least
+ * DEFAULT_CAPACITY.
*/
- final int segmentShift;
+ private static final int MIN_TRANSFER_STRIDE = 16;
- /**
- * The segments, each of which is a specialized hash table.
+ /*
+ * Encodings for Node hash fields. See above for explanation.
*/
- final Segment<K,V>[] segments;
+ static final int MOVED = 0x8fffffff; // (-1) hash for forwarding nodes
+ static final int TREEBIN = 0x80000000; // hash for roots of trees
+ static final int RESERVED = 0x80000001; // hash for transient reservations
+ static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
+
+ /** Number of CPUS, to place bounds on some sizings */
+ static final int NCPU = Runtime.getRuntime().availableProcessors();
- transient Set<K> keySet;
- transient Set<Map.Entry<K,V>> entrySet;
- transient Collection<V> values;
+ /** For serialization compatibility. */
+ private static final ObjectStreamField[] serialPersistentFields = {
+ new ObjectStreamField("segments", Segment[].class),
+ new ObjectStreamField("segmentMask", Integer.TYPE),
+ new ObjectStreamField("segmentShift", Integer.TYPE)
+ };
+
+ /* ---------------- Nodes -------------- */
/**
- * ConcurrentHashMap list entry. Note that this is never exported
- * out as a user-visible Map.Entry.
+ * Key-value entry. This class is never exported out as a
+ * user-mutable Map.Entry (i.e., one supporting setValue; see
+ * MapEntry below), but can be used for read-only traversals used
+ * in bulk tasks. Subclasses of Node with a negative hash field
+ * are special, and contain null keys and values (but are never
+ * exported). Otherwise, keys and vals are never null.
*/
- static final class HashEntry<K,V> {
+ static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
- volatile V value;
- volatile HashEntry<K,V> next;
+ volatile V val;
+ Node<K,V> next;
- HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
+ Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
- this.value = value;
+ this.val = val;
this.next = next;
}
- /**
- * Sets next field with volatile write semantics. (See above
- * about use of putOrderedObject.)
- */
- final void setNext(HashEntry<K,V> n) {
- UNSAFE.putOrderedObject(this, nextOffset, n);
+ public final K getKey() { return key; }
+ public final V getValue() { return val; }
+ public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
+ public final String toString(){ return key + "=" + val; }
+ public final V setValue(V value) {
+ throw new UnsupportedOperationException();
}
- // Unsafe mechanics
- static final sun.misc.Unsafe UNSAFE;
- static final long nextOffset;
- static {
- try {
- UNSAFE = sun.misc.Unsafe.getUnsafe();
- Class<?> k = HashEntry.class;
- nextOffset = UNSAFE.objectFieldOffset
- (k.getDeclaredField("next"));
- } catch (Exception e) {
- throw new Error(e);
+ public final boolean equals(Object o) {
+ Object k, v, u; Map.Entry<?,?> e;
+ return ((o instanceof Map.Entry) &&
+ (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
+ (v = e.getValue()) != null &&
+ (k == key || k.equals(key)) &&
+ (v == (u = val) || v.equals(u)));
+ }
+
+ /**
+ * Virtualized support for map.get(); overridden in subclasses.
+ */
+ Node<K,V> find(int h, Object k) {
+ Node<K,V> e = this;
+ if (k != null) {
+ do {
+ K ek;
+ if (e.hash == h &&
+ ((ek = e.key) == k || (ek != null && k.equals(ek))))
+ return e;
+ } while ((e = e.next) != null);
}
+ return null;
}
}
+ /* ---------------- Static utilities -------------- */
+
/**
- * Gets the ith element of given table (if nonnull) with volatile
- * read semantics. Note: This is manually integrated into a few
- * performance-sensitive methods to reduce call overhead.
+ * Spreads (XORs) higher bits of hash to lower and also forces top
+ * bit to 0. Because the table uses power-of-two masking, sets of
+ * hashes that vary only in bits above the current mask will
+ * always collide. (Among known examples are sets of Float keys
+ * holding consecutive whole numbers in small tables.) So we
+ * apply a transform that spreads the impact of higher bits
+ * downward. There is a tradeoff between speed, utility, and
+ * quality of bit-spreading. Because many common sets of hashes
+ * are already reasonably distributed (so don't benefit from
+ * spreading), and because we use trees to handle large sets of
+ * collisions in bins, we just XOR some shifted bits in the
+ * cheapest possible way to reduce systematic lossage, as well as
+ * to incorporate impact of the highest bits that would otherwise
+ * never be used in index calculations because of table bounds.
*/
- @SuppressWarnings("unchecked")
- static final <K,V> HashEntry<K,V> entryAt(HashEntry<K,V>[] tab, int i) {
- return (tab == null) ? null :
- (HashEntry<K,V>) UNSAFE.getObjectVolatile
- (tab, ((long)i << TSHIFT) + TBASE);
+ static final int spread(int h) {
+ return (h ^ (h >>> 16)) & HASH_BITS;
}
/**
- * Sets the ith element of given table, with volatile write
- * semantics. (See above about use of putOrderedObject.)
+ * Returns a power of two table size for the given desired capacity.
+ * See Hackers Delight, sec 3.2
*/
- static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i,
- HashEntry<K,V> e) {
- UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e);
+ private static final int tableSizeFor(int c) {
+ int n = c - 1;
+ n |= n >>> 1;
+ n |= n >>> 2;
+ n |= n >>> 4;
+ n |= n >>> 8;
+ n |= n >>> 16;
+ return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
+
/**
- * Applies a supplemental hash function to a given hashCode, which
- * defends against poor quality hash functions. This is critical
- * because ConcurrentHashMap uses power-of-two length hash tables,
- * that otherwise encounter collisions for hashCodes that do not
- * differ in lower or upper bits.
+ * Returns x's Class if it is of the form "class C implements
+ * Comparable<C>", else null.
*/
- private static int hash(int h) {
- // Spread bits to regularize both segment and index locations,
- // using variant of single-word Wang/Jenkins hash.
- h += (h << 15) ^ 0xffffcd7d;
- h ^= (h >>> 10);
- h += (h << 3);
- h ^= (h >>> 6);
- h += (h << 2) + (h << 14);
- return h ^ (h >>> 16);
+ static Class<?> comparableClassFor(Object x) {
+ if (x instanceof Comparable) {
+ Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
+ if ((c = x.getClass()) == String.class) // bypass checks
+ return c;
+ if ((ts = c.getGenericInterfaces()) != null) {
+ for (int i = 0; i < ts.length; ++i) {
+ if (((t = ts[i]) instanceof ParameterizedType) &&
+ ((p = (ParameterizedType)t).getRawType() ==
+ Comparable.class) &&
+ (as = p.getActualTypeArguments()) != null &&
+ as.length == 1 && as[0] == c) // type arg is c
+ return c;
+ }
+ }
+ }
+ return null;
}
/**
- * Segments are specialized versions of hash tables. This
- * subclasses from ReentrantLock opportunistically, just to
- * simplify some locking and avoid separate construction.
+ * Returns k.compareTo(x) if x matches kc (k's screened comparable
+ * class), else 0.
*/
- static final class Segment<K,V> extends ReentrantLock implements Serializable {
- /*
- * Segments maintain a table of entry lists that are always
- * kept in a consistent state, so can be read (via volatile
- * reads of segments and tables) without locking. This
- * requires replicating nodes when necessary during table
- * resizing, so the old lists can be traversed by readers
- * still using old version of table.
- *
- * This class defines only mutative methods requiring locking.
- * Except as noted, the methods of this class perform the
- * per-segment versions of ConcurrentHashMap methods. (Other
- * methods are integrated directly into ConcurrentHashMap
- * methods.) These mutative methods use a form of controlled
- * spinning on contention via methods scanAndLock and
- * scanAndLockForPut. These intersperse tryLocks with
- * traversals to locate nodes. The main benefit is to absorb
- * cache misses (which are very common for hash tables) while
- * obtaining locks so that traversal is faster once
- * acquired. We do not actually use the found nodes since they
- * must be re-acquired under lock anyway to ensure sequential
- * consistency of updates (and in any case may be undetectably
- * stale), but they will normally be much faster to re-locate.
- * Also, scanAndLockForPut speculatively creates a fresh node
- * to use in put if no node is found.
- */
+ @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
+ static int compareComparables(Class<?> kc, Object k, Object x) {
+ return (x == null || x.getClass() != kc ? 0 :
+ ((Comparable)k).compareTo(x));
+ }
- private static final long serialVersionUID = 2249069246763182397L;
+ /* ---------------- Table element access -------------- */
- /**
- * The maximum number of times to tryLock in a prescan before
- * possibly blocking on acquire in preparation for a locked
- * segment operation. On multiprocessors, using a bounded
- * number of retries maintains cache acquired while locating
- * nodes.
- */
- static final int MAX_SCAN_RETRIES =
- Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
+ /*
+ * Volatile access methods are used for table elements as well as
+ * elements of in-progress next table while resizing. All uses of
+ * the tab arguments must be null checked by callers. All callers
+ * also paranoically precheck that tab's length is not zero (or an
+ * equivalent check), thus ensuring that any index argument taking
+ * the form of a hash value anded with (length - 1) is a valid
+ * index. Note that, to be correct wrt arbitrary concurrency
+ * errors by users, these checks must operate on local variables,
+ * which accounts for some odd-looking inline assignments below.
+ * Note that calls to setTabAt always occur within locked regions,
+ * and so do not need full volatile semantics, but still require
+ * ordering to maintain concurrent readability.
+ */
- /**
- * The per-segment table. Elements are accessed via
- * entryAt/setEntryAt providing volatile semantics.
- */
- transient volatile HashEntry<K,V>[] table;
+ @SuppressWarnings("unchecked")
+ static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
+ return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
+ }
- /**
- * The number of elements. Accessed only either within locks
- * or among other volatile reads that maintain visibility.
- */
- transient int count;
+ static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
+ Node<K,V> c, Node<K,V> v) {
+ return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
+ }
- /**
- * The total number of mutative operations in this segment.
- * Even though this may overflows 32 bits, it provides
- * sufficient accuracy for stability checks in CHM isEmpty()
- * and size() methods. Accessed only either within locks or
- * among other volatile reads that maintain visibility.
- */
- transient int modCount;
+ static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
+ U.putOrderedObject(tab, ((long)i << ASHIFT) + ABASE, v);
+ }
- /**
- * The table is rehashed when its size exceeds this threshold.
- * (The value of this field is always <tt>(int)(capacity *
- * loadFactor)</tt>.)
- */
- transient int threshold;
+ /* ---------------- Fields -------------- */
- /**
- * The load factor for the hash table. Even though this value
- * is same for all segments, it is replicated to avoid needing
- * links to outer object.
- * @serial
- */
- final float loadFactor;
+ /**
+ * The array of bins. Lazily initialized upon first insertion.
+ * Size is always a power of two. Accessed directly by iterators.
+ */
+ transient volatile Node<K,V>[] table;
- Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
- this.loadFactor = lf;
- this.threshold = threshold;
- this.table = tab;
- }
+ /**
+ * The next table to use; non-null only while resizing.
+ */
+ private transient volatile Node<K,V>[] nextTable;
- final V put(K key, int hash, V value, boolean onlyIfAbsent) {
- HashEntry<K,V> node = tryLock() ? null :
- scanAndLockForPut(key, hash, value);
- V oldValue;
- try {
- HashEntry<K,V>[] tab = table;
- int index = (tab.length - 1) & hash;
- HashEntry<K,V> first = entryAt(tab, index);
- for (HashEntry<K,V> e = first;;) {
- if (e != null) {
- K k;
- if ((k = e.key) == key ||
- (e.hash == hash && key.equals(k))) {
- oldValue = e.value;
- if (!onlyIfAbsent) {
- e.value = value;
- ++modCount;
- }
- break;
- }
- e = e.next;
- }
- else {
- if (node != null)
- node.setNext(first);
- else
- node = new HashEntry<K,V>(hash, key, value, first);
- int c = count + 1;
- if (c > threshold && tab.length < MAXIMUM_CAPACITY)
- rehash(node);
- else
- setEntryAt(tab, index, node);
- ++modCount;
- count = c;
- oldValue = null;
- break;
- }
- }
- } finally {
- unlock();
- }
- return oldValue;
- }
+ /**
+ * Base counter value, used mainly when there is no contention,
+ * but also as a fallback during table initialization
+ * races. Updated via CAS.
+ */
+ private transient volatile long baseCount;
- /**
- * Doubles size of table and repacks entries, also adding the
- * given node to new table
- */
- @SuppressWarnings("unchecked")
- private void rehash(HashEntry<K,V> node) {
- /*
- * Reclassify nodes in each list to new table. Because we
- * are using power-of-two expansion, the elements from
- * each bin must either stay at same index, or move with a
- * power of two offset. We eliminate unnecessary node
- * creation by catching cases where old nodes can be
- * reused because their next fields won't change.
- * Statistically, at the default threshold, only about
- * one-sixth of them need cloning when a table
- * doubles. The nodes they replace will be garbage
- * collectable as soon as they are no longer referenced by
- * any reader thread that may be in the midst of
- * concurrently traversing table. Entry accesses use plain
- * array indexing because they are followed by volatile
- * table write.
- */
- HashEntry<K,V>[] oldTable = table;
- int oldCapacity = oldTable.length;
- int newCapacity = oldCapacity << 1;
- threshold = (int)(newCapacity * loadFactor);
- HashEntry<K,V>[] newTable =
- (HashEntry<K,V>[]) new HashEntry<?,?>[newCapacity];
- int sizeMask = newCapacity - 1;
- for (int i = 0; i < oldCapacity ; i++) {
- HashEntry<K,V> e = oldTable[i];
- if (e != null) {
- HashEntry<K,V> next = e.next;
- int idx = e.hash & sizeMask;
- if (next == null) // Single node on list
- newTable[idx] = e;
- else { // Reuse consecutive sequence at same slot
- HashEntry<K,V> lastRun = e;
- int lastIdx = idx;
- for (HashEntry<K,V> last = next;
- last != null;
- last = last.next) {
- int k = last.hash & sizeMask;
- if (k != lastIdx) {
- lastIdx = k;
- lastRun = last;
- }
- }
- newTable[lastIdx] = lastRun;
- // Clone remaining nodes
- for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
- V v = p.value;
- int h = p.hash;
- int k = h & sizeMask;
- HashEntry<K,V> n = newTable[k];
- newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
- }
- }
- }
- }
- int nodeIndex = node.hash & sizeMask; // add the new node
- node.setNext(newTable[nodeIndex]);
- newTable[nodeIndex] = node;
- table = newTable;
- }
+ /**
+ * Table initialization and resizing control. When negative, the
+ * table is being initialized or resized: -1 for initialization,
+ * else -(1 + the number of active resizing threads). Otherwise,
+ * when table is null, holds the initial table size to use upon
+ * creation, or 0 for default. After initialization, holds the
+ * next element count value upon which to resize the table.
+ */
+ private transient volatile int sizeCtl;
- /**
- * Scans for a node containing given key while trying to
- * acquire lock, creating and returning one if not found. Upon
- * return, guarantees that lock is held. Unlike in most
- * methods, calls to method equals are not screened: Since
- * traversal speed doesn't matter, we might as well help warm
- * up the associated code and accesses as well.
- *
- * @return a new node if key not found, else null
- */
- private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
- HashEntry<K,V> first = entryForHash(this, hash);
- HashEntry<K,V> e = first;
- HashEntry<K,V> node = null;
- int retries = -1; // negative while locating node
- while (!tryLock()) {
- HashEntry<K,V> f; // to recheck first below
- if (retries < 0) {
- if (e == null) {
- if (node == null) // speculatively create node
- node = new HashEntry<K,V>(hash, key, value, null);
- retries = 0;
- }
- else if (key.equals(e.key))
- retries = 0;
- else
- e = e.next;
- }
- else if (++retries > MAX_SCAN_RETRIES) {
- lock();
- break;
- }
- else if ((retries & 1) == 0 &&
- (f = entryForHash(this, hash)) != first) {
- e = first = f; // re-traverse if entry changed
- retries = -1;
- }
- }
- return node;
- }
+ /**
+ * The next table index (plus one) to split while resizing.
+ */
+ private transient volatile int transferIndex;
- /**
- * Scans for a node containing the given key while trying to
- * acquire lock for a remove or replace operation. Upon
- * return, guarantees that lock is held. Note that we must
- * lock even if the key is not found, to ensure sequential
- * consistency of updates.
- */
- private void scanAndLock(Object key, int hash) {
- // similar to but simpler than scanAndLockForPut
- HashEntry<K,V> first = entryForHash(this, hash);
- HashEntry<K,V> e = first;
- int retries = -1;
- while (!tryLock()) {
- HashEntry<K,V> f;
- if (retries < 0) {
- if (e == null || key.equals(e.key))
- retries = 0;
- else
- e = e.next;
- }
- else if (++retries > MAX_SCAN_RETRIES) {
- lock();
- break;
- }
- else if ((retries & 1) == 0 &&
- (f = entryForHash(this, hash)) != first) {
- e = first = f;
- retries = -1;
- }
- }
- }
+ /**
+ * The least available table index to split while resizing.
+ */
+ private transient volatile int transferOrigin;
- /**
- * Remove; match on key only if value null, else match both.
- */
- final V remove(Object key, int hash, Object value) {
- if (!tryLock())
- scanAndLock(key, hash);
- V oldValue = null;
- try {
- HashEntry<K,V>[] tab = table;
- int index = (tab.length - 1) & hash;
- HashEntry<K,V> e = entryAt(tab, index);
- HashEntry<K,V> pred = null;
- while (e != null) {
- K k;
- HashEntry<K,V> next = e.next;
- if ((k = e.key) == key ||
- (e.hash == hash && key.equals(k))) {
- V v = e.value;
- if (value == null || value == v || value.equals(v)) {
- if (pred == null)
- setEntryAt(tab, index, next);
- else
- pred.setNext(next);
- ++modCount;
- --count;
- oldValue = v;
- }
- break;
- }
- pred = e;
- e = next;
- }
- } finally {
- unlock();
- }
- return oldValue;
- }
+ /**
+ * Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
+ */
+ private transient volatile int cellsBusy;
- final boolean replace(K key, int hash, V oldValue, V newValue) {
- if (!tryLock())
- scanAndLock(key, hash);
- boolean replaced = false;
- try {
- HashEntry<K,V> e;
- for (e = entryForHash(this, hash); e != null; e = e.next) {
- K k;
- if ((k = e.key) == key ||
- (e.hash == hash && key.equals(k))) {
- if (oldValue.equals(e.value)) {
- e.value = newValue;
- ++modCount;
- replaced = true;
- }
- break;
- }
- }
- } finally {
- unlock();
- }
- return replaced;
- }
+ /**
+ * Table of counter cells. When non-null, size is a power of 2.
+ */
+ private transient volatile CounterCell[] counterCells;
- final V replace(K key, int hash, V value) {
- if (!tryLock())
- scanAndLock(key, hash);
- V oldValue = null;
- try {
- HashEntry<K,V> e;
- for (e = entryForHash(this, hash); e != null; e = e.next) {
- K k;
- if ((k = e.key) == key ||
- (e.hash == hash && key.equals(k))) {
- oldValue = e.value;
- e.value = value;
- ++modCount;
- break;
- }
- }
- } finally {
- unlock();
- }
- return oldValue;
- }
+ // views
+ private transient KeySetView<K,V> keySet;
+ private transient ValuesView<K,V> values;
+ private transient EntrySetView<K,V> entrySet;
- final void clear() {
- lock();
- try {
- HashEntry<K,V>[] tab = table;
- for (int i = 0; i < tab.length ; i++)
- setEntryAt(tab, i, null);
- ++modCount;
- count = 0;
- } finally {
- unlock();
- }
- }
- }
- // Accessing segments
+ /* ---------------- Public operations -------------- */
/**
- * Gets the jth element of given segment array (if nonnull) with
- * volatile element access semantics via Unsafe. (The null check
- * can trigger harmlessly only during deserialization.) Note:
- * because each element of segments array is set only once (using
- * fully ordered writes), some performance-sensitive methods rely
- * on this method only as a recheck upon null reads.
+ * Creates a new, empty map with the default initial table size (16).
*/
- @SuppressWarnings("unchecked")
- static final <K,V> Segment<K,V> segmentAt(Segment<K,V>[] ss, int j) {
- long u = (j << SSHIFT) + SBASE;
- return ss == null ? null :
- (Segment<K,V>) UNSAFE.getObjectVolatile(ss, u);
+ public ConcurrentHashMap() {
}
/**
- * Returns the segment for the given index, creating it and
- * recording in segment table (via CAS) if not already present.
+ * Creates a new, empty map with an initial table size
+ * accommodating the specified number of elements without the need
+ * to dynamically resize.
*
- * @param k the index
- * @return the segment
- */
- @SuppressWarnings("unchecked")
- private Segment<K,V> ensureSegment(int k) {
- final Segment<K,V>[] ss = this.segments;
- long u = (k << SSHIFT) + SBASE; // raw offset
- Segment<K,V> seg;
- if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
- Segment<K,V> proto = ss[0]; // use segment 0 as prototype
- int cap = proto.table.length;
- float lf = proto.loadFactor;
- int threshold = (int)(cap * lf);
- HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry<?,?>[cap];
- if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
- == null) { // recheck
- Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
- while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
- == null) {
- if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
- break;
- }
- }
- }
- return seg;
- }
-
- // Hash-based segment and entry accesses
-
- /**
- * Gets the segment for the given hash code.
+ * @param initialCapacity The implementation performs internal
+ * sizing to accommodate this many elements.
+ * @throws IllegalArgumentException if the initial capacity of
+ * elements is negative
*/
- @SuppressWarnings("unchecked")
- private Segment<K,V> segmentForHash(int h) {
- long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
- return (Segment<K,V>) UNSAFE.getObjectVolatile(segments, u);
+ public ConcurrentHashMap(int initialCapacity) {
+ if (initialCapacity < 0)
+ throw new IllegalArgumentException();
+ int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
+ MAXIMUM_CAPACITY :
+ tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
+ this.sizeCtl = cap;
}
/**
- * Gets the table entry for the given segment and hash code.
+ * Creates a new map with the same mappings as the given map.
+ *
+ * @param m the map
*/
- @SuppressWarnings("unchecked")
- static final <K,V> HashEntry<K,V> entryForHash(Segment<K,V> seg, int h) {
- HashEntry<K,V>[] tab;
- return (seg == null || (tab = seg.table) == null) ? null :
- (HashEntry<K,V>) UNSAFE.getObjectVolatile
- (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
+ public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
+ this.sizeCtl = DEFAULT_CAPACITY;
+ putAll(m);
}
- /* ---------------- Public operations -------------- */
-
/**
- * Creates a new, empty map with the specified initial
- * capacity, load factor and concurrency level.
+ * Creates a new, empty map with an initial table size based on
+ * the given number of elements ({@code initialCapacity}) and
+ * initial table density ({@code loadFactor}).
*
* @param initialCapacity the initial capacity. The implementation
- * performs internal sizing to accommodate this many elements.
- * @param loadFactor the load factor threshold, used to control resizing.
- * Resizing may be performed when the average number of elements per
- * bin exceeds this threshold.
- * @param concurrencyLevel the estimated number of concurrently
- * updating threads. The implementation performs internal sizing
- * to try to accommodate this many threads.
- * @throws IllegalArgumentException if the initial capacity is
- * negative or the load factor or concurrencyLevel are
- * nonpositive.
- */
- @SuppressWarnings("unchecked")
- public ConcurrentHashMap(int initialCapacity,
- float loadFactor, int concurrencyLevel) {
- if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
- throw new IllegalArgumentException();
- if (concurrencyLevel > MAX_SEGMENTS)
- concurrencyLevel = MAX_SEGMENTS;
- // Find power-of-two sizes best matching arguments
- int sshift = 0;
- int ssize = 1;
- while (ssize < concurrencyLevel) {
- ++sshift;
- ssize <<= 1;
- }
- this.segmentShift = 32 - sshift;
- this.segmentMask = ssize - 1;
- if (initialCapacity > MAXIMUM_CAPACITY)
- initialCapacity = MAXIMUM_CAPACITY;
- int c = initialCapacity / ssize;
- if (c * ssize < initialCapacity)
- ++c;
- int cap = MIN_SEGMENT_TABLE_CAPACITY;
- while (cap < c)
- cap <<= 1;
- // create segments and segments[0]
- Segment<K,V> s0 =
- new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
- (HashEntry<K,V>[])new HashEntry<?,?>[cap]);
- Segment<K,V>[] ss = (Segment<K,V>[])new Segment<?,?>[ssize];
- UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
- this.segments = ss;
- }
-
- /**
- * Creates a new, empty map with the specified initial capacity
- * and load factor and with the default concurrencyLevel (16).
- *
- * @param initialCapacity The implementation performs internal
- * sizing to accommodate this many elements.
- * @param loadFactor the load factor threshold, used to control resizing.
- * Resizing may be performed when the average number of elements per
- * bin exceeds this threshold.
+ * performs internal sizing to accommodate this many elements,
+ * given the specified load factor.
+ * @param loadFactor the load factor (table density) for
+ * establishing the initial table size
* @throws IllegalArgumentException if the initial capacity of
* elements is negative or the load factor is nonpositive
*
* @since 1.6
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
- this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
+ this(initialCapacity, loadFactor, 1);
}
/**
- * Creates a new, empty map with the specified initial capacity,
- * and with default load factor (0.75) and concurrencyLevel (16).
+ * Creates a new, empty map with an initial table size based on
+ * the given number of elements ({@code initialCapacity}), table
+ * density ({@code loadFactor}), and number of concurrently
+ * updating threads ({@code concurrencyLevel}).
*
* @param initialCapacity the initial capacity. The implementation
- * performs internal sizing to accommodate this many elements.
- * @throws IllegalArgumentException if the initial capacity of
- * elements is negative.
+ * performs internal sizing to accommodate this many elements,
+ * given the specified load factor.
+ * @param loadFactor the load factor (table density) for
+ * establishing the initial table size
+ * @param concurrencyLevel the estimated number of concurrently
+ * updating threads. The implementation may use this value as
+ * a sizing hint.
+ * @throws IllegalArgumentException if the initial capacity is
+ * negative or the load factor or concurrencyLevel are
+ * nonpositive
*/
- public ConcurrentHashMap(int initialCapacity) {
- this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
+ public ConcurrentHashMap(int initialCapacity,
+ float loadFactor, int concurrencyLevel) {
+ if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
+ throw new IllegalArgumentException();
+ if (initialCapacity < concurrencyLevel) // Use at least as many bins
+ initialCapacity = concurrencyLevel; // as estimated threads
+ long size = (long)(1.0 + (long)initialCapacity / loadFactor);
+ int cap = (size >= (long)MAXIMUM_CAPACITY) ?
+ MAXIMUM_CAPACITY : tableSizeFor((int)size);
+ this.sizeCtl = cap;
}
- /**
- * Creates a new, empty map with a default initial capacity (16),
- * load factor (0.75) and concurrencyLevel (16).
- */
- public ConcurrentHashMap() {
- this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
- }
+ // Original (since JDK1.2) Map methods
/**
- * Creates a new map with the same mappings as the given map.
- * The map is created with a capacity of 1.5 times the number
- * of mappings in the given map or 16 (whichever is greater),
- * and a default load factor (0.75) and concurrencyLevel (16).
- *
- * @param m the map
+ * {@inheritDoc}
*/
- public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
- this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
- DEFAULT_INITIAL_CAPACITY),
- DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
- putAll(m);
+ public int size() {
+ long n = sumCount();
+ return ((n < 0L) ? 0 :
+ (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
+ (int)n);
}
/**
- * Returns <tt>true</tt> if this map contains no key-value mappings.
- *
- * @return <tt>true</tt> if this map contains no key-value mappings
+ * {@inheritDoc}
*/
public boolean isEmpty() {
- /*
- * Sum per-segment modCounts to avoid mis-reporting when
- * elements are concurrently added and removed in one segment
- * while checking another, in which case the table was never
- * actually empty at any point. (The sum ensures accuracy up
- * through at least 1<<31 per-segment modifications before
- * recheck.) Methods size() and containsValue() use similar
- * constructions for stability checks.
- */
- long sum = 0L;
- final Segment<K,V>[] segments = this.segments;
- for (int j = 0; j < segments.length; ++j) {
- Segment<K,V> seg = segmentAt(segments, j);
- if (seg != null) {
- if (seg.count != 0)
- return false;
- sum += seg.modCount;
- }
- }
- if (sum != 0L) { // recheck unless no modifications
- for (int j = 0; j < segments.length; ++j) {
- Segment<K,V> seg = segmentAt(segments, j);
- if (seg != null) {
- if (seg.count != 0)
- return false;
- sum -= seg.modCount;
- }
- }
- if (sum != 0L)
- return false;
- }
- return true;
- }
-
- /**
- * Returns the number of key-value mappings in this map. If the
- * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
- * <tt>Integer.MAX_VALUE</tt>.
- *
- * @return the number of key-value mappings in this map
- */
- public int size() {
- // Try a few times to get accurate count. On failure due to
- // continuous async changes in table, resort to locking.
- final Segment<K,V>[] segments = this.segments;
- final int segmentCount = segments.length;
-
- long previousSum = 0L;
- for (int retries = -1; retries < RETRIES_BEFORE_LOCK; retries++) {
- long sum = 0L; // sum of modCounts
- long size = 0L;
- for (int i = 0; i < segmentCount; i++) {
- Segment<K,V> segment = segmentAt(segments, i);
- if (segment != null) {
- sum += segment.modCount;
- size += segment.count;
- }
- }
- if (sum == previousSum)
- return ((size >>> 31) == 0) ? (int) size : Integer.MAX_VALUE;
- previousSum = sum;
- }
-
- long size = 0L;
- for (int i = 0; i < segmentCount; i++) {
- Segment<K,V> segment = ensureSegment(i);
- segment.lock();
- size += segment.count;
- }
- for (int i = 0; i < segmentCount; i++)
- segments[i].unlock();
- return ((size >>> 31) == 0) ? (int) size : Integer.MAX_VALUE;
+ return sumCount() <= 0L; // ignore transient negative values
}
/**
@@ -878,18 +742,20 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
- Segment<K,V> s; // manually integrate access methods to reduce overhead
- HashEntry<K,V>[] tab;
- int h = hash(key.hashCode());
- long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
- if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
- (tab = s.table) != null) {
- for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
- (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
- e != null; e = e.next) {
- K k;
- if ((k = e.key) == key || (e.hash == h && key.equals(k)))
- return e.value;
+ Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
+ int h = spread(key.hashCode());
+ if ((tab = table) != null && (n = tab.length) > 0 &&
+ (e = tabAt(tab, (n - 1) & h)) != null) {
+ if ((eh = e.hash) == h) {
+ if ((ek = e.key) == key || (ek != null && key.equals(ek)))
+ return e.val;
+ }
+ else if (eh < 0)
+ return (p = e.find(h, key)) != null ? p.val : null;
+ while ((e = e.next) != null) {
+ if (e.hash == h &&
+ ((ek = e.key) == key || (ek != null && key.equals(ek))))
+ return e.val;
}
}
return null;
@@ -898,149 +764,121 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
/**
* Tests if the specified object is a key in this table.
*
- * @param key possible key
- * @return <tt>true</tt> if and only if the specified object
+ * @param key possible key
+ * @return {@code true} if and only if the specified object
* is a key in this table, as determined by the
- * <tt>equals</tt> method; <tt>false</tt> otherwise.
+ * {@code equals} method; {@code false} otherwise
* @throws NullPointerException if the specified key is null
*/
- @SuppressWarnings("unchecked")
public boolean containsKey(Object key) {
- Segment<K,V> s; // same as get() except no need for volatile value read
- HashEntry<K,V>[] tab;
- int h = hash(key.hashCode());
- long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
- if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
- (tab = s.table) != null) {
- for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
- (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
- e != null; e = e.next) {
- K k;
- if ((k = e.key) == key || (e.hash == h && key.equals(k)))
- return true;
- }
- }
- return false;
+ return get(key) != null;
}
/**
- * Returns <tt>true</tt> if this map maps one or more keys to the
- * specified value. Note: This method requires a full internal
- * traversal of the hash table, and so is much slower than
- * method <tt>containsKey</tt>.
+ * Returns {@code true} if this map maps one or more keys to the
+ * specified value. Note: This method may require a full traversal
+ * of the map, and is much slower than method {@code containsKey}.
*
* @param value value whose presence in this map is to be tested
- * @return <tt>true</tt> if this map maps one or more keys to the
+ * @return {@code true} if this map maps one or more keys to the
* specified value
* @throws NullPointerException if the specified value is null
*/
public boolean containsValue(Object value) {
- // Same idea as size()
if (value == null)
throw new NullPointerException();
- final Segment<K,V>[] segments = this.segments;
- long previousSum = 0L;
- int lockCount = 0;
- try {
- for (int retries = -1; ; retries++) {
- long sum = 0L; // sum of modCounts
- for (int j = 0; j < segments.length; j++) {
- Segment<K,V> segment;
- if (retries == RETRIES_BEFORE_LOCK) {
- segment = ensureSegment(j);
- segment.lock();
- lockCount++;
- } else {
- segment = segmentAt(segments, j);
- if (segment == null)
- continue;
- }
- HashEntry<K,V>[] tab = segment.table;
- if (tab != null) {
- for (int i = 0 ; i < tab.length; i++) {
- HashEntry<K,V> e;
- for (e = entryAt(tab, i); e != null; e = e.next) {
- V v = e.value;
- if (v != null && value.equals(v))
- return true;
- }
- }
- sum += segment.modCount;
- }
- }
- if ((retries >= 0 && sum == previousSum) || lockCount > 0)
- return false;
- previousSum = sum;
+ Node<K,V>[] t;
+ if ((t = table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> p; (p = it.advance()) != null; ) {
+ V v;
+ if ((v = p.val) == value || (v != null && value.equals(v)))
+ return true;
}
- } finally {
- for (int j = 0; j < lockCount; j++)
- segments[j].unlock();
}
- }
-
- /**
- * Legacy method testing if some key maps into the specified value
- * in this table. This method is identical in functionality to
- * {@link #containsValue}, and exists solely to ensure
- * full compatibility with class {@link java.util.Hashtable},
- * which supported this method prior to introduction of the
- * Java Collections framework.
- *
- * @param value a value to search for
- * @return <tt>true</tt> if and only if some key maps to the
- * <tt>value</tt> argument in this table as
- * determined by the <tt>equals</tt> method;
- * <tt>false</tt> otherwise
- * @throws NullPointerException if the specified value is null
- */
- public boolean contains(Object value) {
- return containsValue(value);
+ return false;
}
/**
* Maps the specified key to the specified value in this table.
* Neither the key nor the value can be null.
*
- * <p> The value can be retrieved by calling the <tt>get</tt> method
+ * <p>The value can be retrieved by calling the {@code get} method
* with a key that is equal to the original key.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
- * @return the previous value associated with <tt>key</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>key</tt>
+ * @return the previous value associated with {@code key}, or
+ * {@code null} if there was no mapping for {@code key}
* @throws NullPointerException if the specified key or value is null
*/
- @SuppressWarnings("unchecked")
public V put(K key, V value) {
- Segment<K,V> s;
- if (value == null)
- throw new NullPointerException();
- int hash = hash(key.hashCode());
- int j = (hash >>> segmentShift) & segmentMask;
- if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
- (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
- s = ensureSegment(j);
- return s.put(key, hash, value, false);
+ return putVal(key, value, false);
}
- /**
- * {@inheritDoc}
- *
- * @return the previous value associated with the specified key,
- * or <tt>null</tt> if there was no mapping for the key
- * @throws NullPointerException if the specified key or value is null
- */
- @SuppressWarnings("unchecked")
- public V putIfAbsent(K key, V value) {
- Segment<K,V> s;
- if (value == null)
- throw new NullPointerException();
- int hash = hash(key.hashCode());
- int j = (hash >>> segmentShift) & segmentMask;
- if ((s = (Segment<K,V>)UNSAFE.getObject
- (segments, (j << SSHIFT) + SBASE)) == null)
- s = ensureSegment(j);
- return s.put(key, hash, value, true);
+ /** Implementation for put and putIfAbsent */
+ final V putVal(K key, V value, boolean onlyIfAbsent) {
+ if (key == null || value == null) throw new NullPointerException();
+ int hash = spread(key.hashCode());
+ int binCount = 0;
+ for (Node<K,V>[] tab = table;;) {
+ Node<K,V> f; int n, i, fh;
+ if (tab == null || (n = tab.length) == 0)
+ tab = initTable();
+ else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
+ if (casTabAt(tab, i, null,
+ new Node<K,V>(hash, key, value, null)))
+ break; // no lock when adding to empty bin
+ }
+ else if ((fh = f.hash) == MOVED)
+ tab = helpTransfer(tab, f);
+ else {
+ V oldVal = null;
+ synchronized (f) {
+ if (tabAt(tab, i) == f) {
+ if (fh >= 0) {
+ binCount = 1;
+ for (Node<K,V> e = f;; ++binCount) {
+ K ek;
+ if (e.hash == hash &&
+ ((ek = e.key) == key ||
+ (ek != null && key.equals(ek)))) {
+ oldVal = e.val;
+ if (!onlyIfAbsent)
+ e.val = value;
+ break;
+ }
+ Node<K,V> pred = e;
+ if ((e = e.next) == null) {
+ pred.next = new Node<K,V>(hash, key,
+ value, null);
+ break;
+ }
+ }
+ }
+ else if (f instanceof TreeBin) {
+ Node<K,V> p;
+ binCount = 2;
+ if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
+ value)) != null) {
+ oldVal = p.val;
+ if (!onlyIfAbsent)
+ p.val = value;
+ }
+ }
+ }
+ }
+ if (binCount != 0) {
+ if (binCount >= TREEIFY_THRESHOLD)
+ treeifyBin(tab, i);
+ if (oldVal != null)
+ return oldVal;
+ break;
+ }
+ }
+ }
+ addCount(1L, binCount);
+ return null;
}
/**
@@ -1051,8 +889,9 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
* @param m mappings to be stored in this map
*/
public void putAll(Map<? extends K, ? extends V> m) {
+ tryPresize(m.size());
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
- put(e.getKey(), e.getValue());
+ putVal(e.getKey(), e.getValue(), false);
}
/**
@@ -1060,87 +899,147 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
* This method does nothing if the key is not in the map.
*
* @param key the key that needs to be removed
- * @return the previous value associated with <tt>key</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>key</tt>
+ * @return the previous value associated with {@code key}, or
+ * {@code null} if there was no mapping for {@code key}
* @throws NullPointerException if the specified key is null
*/
public V remove(Object key) {
- int hash = hash(key.hashCode());
- Segment<K,V> s = segmentForHash(hash);
- return s == null ? null : s.remove(key, hash, null);
- }
-
- /**
- * {@inheritDoc}
- *
- * @throws NullPointerException if the specified key is null
- */
- public boolean remove(Object key, Object value) {
- int hash = hash(key.hashCode());
- Segment<K,V> s;
- return value != null && (s = segmentForHash(hash)) != null &&
- s.remove(key, hash, value) != null;
- }
-
- /**
- * {@inheritDoc}
- *
- * @throws NullPointerException if any of the arguments are null
- */
- public boolean replace(K key, V oldValue, V newValue) {
- int hash = hash(key.hashCode());
- if (oldValue == null || newValue == null)
- throw new NullPointerException();
- Segment<K,V> s = segmentForHash(hash);
- return s != null && s.replace(key, hash, oldValue, newValue);
+ return replaceNode(key, null, null);
}
/**
- * {@inheritDoc}
- *
- * @return the previous value associated with the specified key,
- * or <tt>null</tt> if there was no mapping for the key
- * @throws NullPointerException if the specified key or value is null
+ * Implementation for the four public remove/replace methods:
+ * Replaces node value with v, conditional upon match of cv if
+ * non-null. If resulting value is null, delete.
*/
- public V replace(K key, V value) {
- int hash = hash(key.hashCode());
- if (value == null)
- throw new NullPointerException();
- Segment<K,V> s = segmentForHash(hash);
- return s == null ? null : s.replace(key, hash, value);
+ final V replaceNode(Object key, V value, Object cv) {
+ int hash = spread(key.hashCode());
+ for (Node<K,V>[] tab = table;;) {
+ Node<K,V> f; int n, i, fh;
+ if (tab == null || (n = tab.length) == 0 ||
+ (f = tabAt(tab, i = (n - 1) & hash)) == null)
+ break;
+ else if ((fh = f.hash) == MOVED)
+ tab = helpTransfer(tab, f);
+ else {
+ V oldVal = null;
+ boolean validated = false;
+ synchronized (f) {
+ if (tabAt(tab, i) == f) {
+ if (fh >= 0) {
+ validated = true;
+ for (Node<K,V> e = f, pred = null;;) {
+ K ek;
+ if (e.hash == hash &&
+ ((ek = e.key) == key ||
+ (ek != null && key.equals(ek)))) {
+ V ev = e.val;
+ if (cv == null || cv == ev ||
+ (ev != null && cv.equals(ev))) {
+ oldVal = ev;
+ if (value != null)
+ e.val = value;
+ else if (pred != null)
+ pred.next = e.next;
+ else
+ setTabAt(tab, i, e.next);
+ }
+ break;
+ }
+ pred = e;
+ if ((e = e.next) == null)
+ break;
+ }
+ }
+ else if (f instanceof TreeBin) {
+ validated = true;
+ TreeBin<K,V> t = (TreeBin<K,V>)f;
+ TreeNode<K,V> r, p;
+ if ((r = t.root) != null &&
+ (p = r.findTreeNode(hash, key, null)) != null) {
+ V pv = p.val;
+ if (cv == null || cv == pv ||
+ (pv != null && cv.equals(pv))) {
+ oldVal = pv;
+ if (value != null)
+ p.val = value;
+ else if (t.removeTreeNode(p))
+ setTabAt(tab, i, untreeify(t.first));
+ }
+ }
+ }
+ }
+ }
+ if (validated) {
+ if (oldVal != null) {
+ if (value == null)
+ addCount(-1L, -1);
+ return oldVal;
+ }
+ break;
+ }
+ }
+ }
+ return null;
}
/**
* Removes all of the mappings from this map.
*/
public void clear() {
- final Segment<K,V>[] segments = this.segments;
- for (int j = 0; j < segments.length; ++j) {
- Segment<K,V> s = segmentAt(segments, j);
- if (s != null)
- s.clear();
+ long delta = 0L; // negative number of deletions
+ int i = 0;
+ Node<K,V>[] tab = table;
+ while (tab != null && i < tab.length) {
+ int fh;
+ Node<K,V> f = tabAt(tab, i);
+ if (f == null)
+ ++i;
+ else if ((fh = f.hash) == MOVED) {
+ tab = helpTransfer(tab, f);
+ i = 0; // restart
+ }
+ else {
+ synchronized (f) {
+ if (tabAt(tab, i) == f) {
+ Node<K,V> p = (fh >= 0 ? f :
+ (f instanceof TreeBin) ?
+ ((TreeBin<K,V>)f).first : null);
+ while (p != null) {
+ --delta;
+ p = p.next;
+ }
+ setTabAt(tab, i++, null);
+ }
+ }
+ }
}
+ if (delta != 0L)
+ addCount(delta, -1);
}
/**
* Returns a {@link Set} view of the keys contained in this map.
* The set is backed by the map, so changes to the map are
- * reflected in the set, and vice-versa. The set supports element
+ * reflected in the set, and vice-versa. The set supports element
* removal, which removes the corresponding mapping from this map,
- * via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
- * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
- * operations. It does not support the <tt>add</tt> or
- * <tt>addAll</tt> operations.
+ * via the {@code Iterator.remove}, {@code Set.remove},
+ * {@code removeAll}, {@code retainAll}, and {@code clear}
+ * operations. It does not support the {@code add} or
+ * {@code addAll} operations.
*
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator
+ * <p>The view's {@code iterator} is a "weakly consistent" iterator
* that will never throw {@link ConcurrentModificationException},
* and guarantees to traverse elements as they existed upon
* construction of the iterator, and may (but is not guaranteed to)
* reflect any modifications subsequent to construction.
+ *
+ * @return the set view
+ *
*/
public Set<K> keySet() {
- Set<K> ks = keySet;
- return (ks != null) ? ks : (keySet = new KeySet());
+ KeySetView<K,V> ks;
+ return (ks = keySet) != null ? ks : (keySet = new KeySetView<K,V>(this, null));
}
/**
@@ -1148,20 +1047,22 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
* The collection is backed by the map, so changes to the map are
* reflected in the collection, and vice-versa. The collection
* supports element removal, which removes the corresponding
- * mapping from this map, via the <tt>Iterator.remove</tt>,
- * <tt>Collection.remove</tt>, <tt>removeAll</tt>,
- * <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not
- * support the <tt>add</tt> or <tt>addAll</tt> operations.
+ * mapping from this map, via the {@code Iterator.remove},
+ * {@code Collection.remove}, {@code removeAll},
+ * {@code retainAll}, and {@code clear} operations. It does not
+ * support the {@code add} or {@code addAll} operations.
*
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator
+ * <p>The view's {@code iterator} is a "weakly consistent" iterator
* that will never throw {@link ConcurrentModificationException},
* and guarantees to traverse elements as they existed upon
* construction of the iterator, and may (but is not guaranteed to)
* reflect any modifications subsequent to construction.
+ *
+ * @return the collection view
*/
public Collection<V> values() {
- Collection<V> vs = values;
- return (vs != null) ? vs : (values = new Values());
+ ValuesView<K,V> vs;
+ return (vs = values) != null ? vs : (values = new ValuesView<K,V>(this));
}
/**
@@ -1169,20 +1070,329 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. The set supports element
* removal, which removes the corresponding mapping from the map,
- * via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
- * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
- * operations. It does not support the <tt>add</tt> or
- * <tt>addAll</tt> operations.
+ * via the {@code Iterator.remove}, {@code Set.remove},
+ * {@code removeAll}, {@code retainAll}, and {@code clear}
+ * operations.
*
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator
+ * <p>The view's {@code iterator} is a "weakly consistent" iterator
* that will never throw {@link ConcurrentModificationException},
* and guarantees to traverse elements as they existed upon
* construction of the iterator, and may (but is not guaranteed to)
* reflect any modifications subsequent to construction.
+ *
+ * @return the set view
*/
public Set<Map.Entry<K,V>> entrySet() {
- Set<Map.Entry<K,V>> es = entrySet;
- return (es != null) ? es : (entrySet = new EntrySet());
+ EntrySetView<K,V> es;
+ return (es = entrySet) != null ? es : (entrySet = new EntrySetView<K,V>(this));
+ }
+
+ /**
+ * Returns the hash code value for this {@link Map}, i.e.,
+ * the sum of, for each key-value pair in the map,
+ * {@code key.hashCode() ^ value.hashCode()}.
+ *
+ * @return the hash code value for this map
+ */
+ public int hashCode() {
+ int h = 0;
+ Node<K,V>[] t;
+ if ((t = table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> p; (p = it.advance()) != null; )
+ h += p.key.hashCode() ^ p.val.hashCode();
+ }
+ return h;
+ }
+
+ /**
+ * Returns a string representation of this map. The string
+ * representation consists of a list of key-value mappings (in no
+ * particular order) enclosed in braces ("{@code {}}"). Adjacent
+ * mappings are separated by the characters {@code ", "} (comma
+ * and space). Each key-value mapping is rendered as the key
+ * followed by an equals sign ("{@code =}") followed by the
+ * associated value.
+ *
+ * @return a string representation of this map
+ */
+ public String toString() {
+ Node<K,V>[] t;
+ int f = (t = table) == null ? 0 : t.length;
+ Traverser<K,V> it = new Traverser<K,V>(t, f, 0, f);
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ Node<K,V> p;
+ if ((p = it.advance()) != null) {
+ for (;;) {
+ K k = p.key;
+ V v = p.val;
+ sb.append(k == this ? "(this Map)" : k);
+ sb.append('=');
+ sb.append(v == this ? "(this Map)" : v);
+ if ((p = it.advance()) == null)
+ break;
+ sb.append(',').append(' ');
+ }
+ }
+ return sb.append('}').toString();
+ }
+
+ /**
+ * Compares the specified object with this map for equality.
+ * Returns {@code true} if the given object is a map with the same
+ * mappings as this map. This operation may return misleading
+ * results if either map is concurrently modified during execution
+ * of this method.
+ *
+ * @param o object to be compared for equality with this map
+ * @return {@code true} if the specified object is equal to this map
+ */
+ public boolean equals(Object o) {
+ if (o != this) {
+ if (!(o instanceof Map))
+ return false;
+ Map<?,?> m = (Map<?,?>) o;
+ Node<K,V>[] t;
+ int f = (t = table) == null ? 0 : t.length;
+ Traverser<K,V> it = new Traverser<K,V>(t, f, 0, f);
+ for (Node<K,V> p; (p = it.advance()) != null; ) {
+ V val = p.val;
+ Object v = m.get(p.key);
+ if (v == null || (v != val && !v.equals(val)))
+ return false;
+ }
+ for (Map.Entry<?,?> e : m.entrySet()) {
+ Object mk, mv, v;
+ if ((mk = e.getKey()) == null ||
+ (mv = e.getValue()) == null ||
+ (v = get(mk)) == null ||
+ (mv != v && !mv.equals(v)))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Stripped-down version of helper class used in previous version,
+ * declared for the sake of serialization compatibility
+ */
+ static class Segment<K,V> extends ReentrantLock implements Serializable {
+ private static final long serialVersionUID = 2249069246763182397L;
+ final float loadFactor;
+ Segment(float lf) { this.loadFactor = lf; }
+ }
+
+ /**
+ * Saves the state of the {@code ConcurrentHashMap} instance to a
+ * stream (i.e., serializes it).
+ * @param s the stream
+ * @serialData
+ * the key (Object) and value (Object)
+ * for each key-value mapping, followed by a null pair.
+ * The key-value mappings are emitted in no particular order.
+ */
+ private void writeObject(java.io.ObjectOutputStream s)
+ throws java.io.IOException {
+ // For serialization compatibility
+ // Emulate segment calculation from previous version of this class
+ int sshift = 0;
+ int ssize = 1;
+ while (ssize < DEFAULT_CONCURRENCY_LEVEL) {
+ ++sshift;
+ ssize <<= 1;
+ }
+ int segmentShift = 32 - sshift;
+ int segmentMask = ssize - 1;
+ @SuppressWarnings("unchecked") Segment<K,V>[] segments = (Segment<K,V>[])
+ new Segment<?,?>[DEFAULT_CONCURRENCY_LEVEL];
+ for (int i = 0; i < segments.length; ++i)
+ segments[i] = new Segment<K,V>(LOAD_FACTOR);
+ s.putFields().put("segments", segments);
+ s.putFields().put("segmentShift", segmentShift);
+ s.putFields().put("segmentMask", segmentMask);
+ s.writeFields();
+
+ Node<K,V>[] t;
+ if ((t = table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> p; (p = it.advance()) != null; ) {
+ s.writeObject(p.key);
+ s.writeObject(p.val);
+ }
+ }
+ s.writeObject(null);
+ s.writeObject(null);
+ segments = null; // throw away
+ }
+
+ /**
+ * Reconstitutes the instance from a stream (that is, deserializes it).
+ * @param s the stream
+ */
+ private void readObject(java.io.ObjectInputStream s)
+ throws java.io.IOException, ClassNotFoundException {
+ /*
+ * To improve performance in typical cases, we create nodes
+ * while reading, then place in table once size is known.
+ * However, we must also validate uniqueness and deal with
+ * overpopulated bins while doing so, which requires
+ * specialized versions of putVal mechanics.
+ */
+ sizeCtl = -1; // force exclusion for table construction
+ s.defaultReadObject();
+ long size = 0L;
+ Node<K,V> p = null;
+ for (;;) {
+ @SuppressWarnings("unchecked") K k = (K) s.readObject();
+ @SuppressWarnings("unchecked") V v = (V) s.readObject();
+ if (k != null && v != null) {
+ p = new Node<K,V>(spread(k.hashCode()), k, v, p);
+ ++size;
+ }
+ else
+ break;
+ }
+ if (size == 0L)
+ sizeCtl = 0;
+ else {
+ int n;
+ if (size >= (long)(MAXIMUM_CAPACITY >>> 1))
+ n = MAXIMUM_CAPACITY;
+ else {
+ int sz = (int)size;
+ n = tableSizeFor(sz + (sz >>> 1) + 1);
+ }
+ @SuppressWarnings({"rawtypes","unchecked"})
+ Node<K,V>[] tab = (Node<K,V>[])new Node[n];
+ int mask = n - 1;
+ long added = 0L;
+ while (p != null) {
+ boolean insertAtFront;
+ Node<K,V> next = p.next, first;
+ int h = p.hash, j = h & mask;
+ if ((first = tabAt(tab, j)) == null)
+ insertAtFront = true;
+ else {
+ K k = p.key;
+ if (first.hash < 0) {
+ TreeBin<K,V> t = (TreeBin<K,V>)first;
+ if (t.putTreeVal(h, k, p.val) == null)
+ ++added;
+ insertAtFront = false;
+ }
+ else {
+ int binCount = 0;
+ insertAtFront = true;
+ Node<K,V> q; K qk;
+ for (q = first; q != null; q = q.next) {
+ if (q.hash == h &&
+ ((qk = q.key) == k ||
+ (qk != null && k.equals(qk)))) {
+ insertAtFront = false;
+ break;
+ }
+ ++binCount;
+ }
+ if (insertAtFront && binCount >= TREEIFY_THRESHOLD) {
+ insertAtFront = false;
+ ++added;
+ p.next = first;
+ TreeNode<K,V> hd = null, tl = null;
+ for (q = p; q != null; q = q.next) {
+ TreeNode<K,V> t = new TreeNode<K,V>
+ (q.hash, q.key, q.val, null, null);
+ if ((t.prev = tl) == null)
+ hd = t;
+ else
+ tl.next = t;
+ tl = t;
+ }
+ setTabAt(tab, j, new TreeBin<K,V>(hd));
+ }
+ }
+ }
+ if (insertAtFront) {
+ ++added;
+ p.next = first;
+ setTabAt(tab, j, p);
+ }
+ p = next;
+ }
+ table = tab;
+ sizeCtl = n - (n >>> 2);
+ baseCount = added;
+ }
+ }
+
+ // ConcurrentMap methods
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return the previous value associated with the specified key,
+ * or {@code null} if there was no mapping for the key
+ * @throws NullPointerException if the specified key or value is null
+ */
+ public V putIfAbsent(K key, V value) {
+ return putVal(key, value, true);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws NullPointerException if the specified key is null
+ */
+ public boolean remove(Object key, Object value) {
+ if (key == null)
+ throw new NullPointerException();
+ return value != null && replaceNode(key, null, value) != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws NullPointerException if any of the arguments are null
+ */
+ public boolean replace(K key, V oldValue, V newValue) {
+ if (key == null || oldValue == null || newValue == null)
+ throw new NullPointerException();
+ return replaceNode(key, newValue, oldValue) != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return the previous value associated with the specified key,
+ * or {@code null} if there was no mapping for the key
+ * @throws NullPointerException if the specified key or value is null
+ */
+ public V replace(K key, V value) {
+ if (key == null || value == null)
+ throw new NullPointerException();
+ return replaceNode(key, value, null);
+ }
+ // Hashtable legacy methods
+
+ /**
+ * Legacy method testing if some key maps into the specified value
+ * in this table. This method is identical in functionality to
+ * {@link #containsValue(Object)}, and exists solely to ensure
+ * full compatibility with class {@link java.util.Hashtable}.
+ *
+ * @param value a value to search for
+ * @return {@code true} if and only if some key maps to the
+ * {@code value} argument in this table as
+ * determined by the {@code equals} method;
+ * {@code false} otherwise
+ * @throws NullPointerException if the specified value is null
+ */
+ public boolean contains(Object value) {
+ // BEGIN android-note
+ // removed deprecation
+ // END android-note
+ return containsValue(value);
}
/**
@@ -1192,7 +1402,9 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
* @see #keySet()
*/
public Enumeration<K> keys() {
- return new KeyIterator();
+ Node<K,V>[] t;
+ int f = (t = table) == null ? 0 : t.length;
+ return new KeyIterator<K,V>(t, f, 0, f, this);
}
/**
@@ -1202,281 +1414,1787 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
* @see #values()
*/
public Enumeration<V> elements() {
- return new ValueIterator();
+ Node<K,V>[] t;
+ int f = (t = table) == null ? 0 : t.length;
+ return new ValueIterator<K,V>(t, f, 0, f, this);
}
- /* ---------------- Iterator Support -------------- */
+ // ConcurrentHashMap-only methods
- abstract class HashIterator {
- int nextSegmentIndex;
- int nextTableIndex;
- HashEntry<K,V>[] currentTable;
- HashEntry<K, V> nextEntry;
- HashEntry<K, V> lastReturned;
+ /**
+ * Returns the number of mappings. This method should be used
+ * instead of {@link #size} because a ConcurrentHashMap may
+ * contain more mappings than can be represented as an int. The
+ * value returned is an estimate; the actual count may differ if
+ * there are concurrent insertions or removals.
+ *
+ * @return the number of mappings
+ * @since 1.8
+ *
+ * @hide
+ */
+ public long mappingCount() {
+ long n = sumCount();
+ return (n < 0L) ? 0L : n; // ignore transient negative values
+ }
- HashIterator() {
- nextSegmentIndex = segments.length - 1;
- nextTableIndex = -1;
- advance();
+ /**
+ * Creates a new {@link Set} backed by a ConcurrentHashMap
+ * from the given type to {@code Boolean.TRUE}.
+ *
+ * @return the new set
+ * @since 1.8
+ *
+ * @hide
+ */
+ public static <K> KeySetView<K,Boolean> newKeySet() {
+ return new KeySetView<K,Boolean>
+ (new ConcurrentHashMap<K,Boolean>(), Boolean.TRUE);
+ }
+
+ /**
+ * Creates a new {@link Set} backed by a ConcurrentHashMap
+ * from the given type to {@code Boolean.TRUE}.
+ *
+ * @param initialCapacity The implementation performs internal
+ * sizing to accommodate this many elements.
+ * @throws IllegalArgumentException if the initial capacity of
+ * elements is negative
+ * @return the new set
+ * @since 1.8
+ *
+ * @hide
+ */
+ public static <K> KeySetView<K,Boolean> newKeySet(int initialCapacity) {
+ return new KeySetView<K,Boolean>
+ (new ConcurrentHashMap<K,Boolean>(initialCapacity), Boolean.TRUE);
+ }
+
+ /**
+ * Returns a {@link Set} view of the keys in this map, using the
+ * given common mapped value for any additions (i.e., {@link
+ * Collection#add} and {@link Collection#addAll(Collection)}).
+ * This is of course only appropriate if it is acceptable to use
+ * the same value for all additions from this view.
+ *
+ * @param mappedValue the mapped value to use for any additions
+ * @return the set view
+ * @throws NullPointerException if the mappedValue is null
+ *
+ * @hide
+ */
+ public Set<K> keySet(V mappedValue) {
+ if (mappedValue == null)
+ throw new NullPointerException();
+ return new KeySetView<K,V>(this, mappedValue);
+ }
+
+ /* ---------------- Special Nodes -------------- */
+
+ /**
+ * A node inserted at head of bins during transfer operations.
+ */
+ static final class ForwardingNode<K,V> extends Node<K,V> {
+ final Node<K,V>[] nextTable;
+ ForwardingNode(Node<K,V>[] tab) {
+ super(MOVED, null, null, null);
+ this.nextTable = tab;
+ }
+
+ Node<K,V> find(int h, Object k) {
+ Node<K,V> e; int n;
+ Node<K,V>[] tab = nextTable;
+ if (k != null && tab != null && (n = tab.length) > 0 &&
+ (e = tabAt(tab, (n - 1) & h)) != null) {
+ do {
+ int eh; K ek;
+ if ((eh = e.hash) == h &&
+ ((ek = e.key) == k || (ek != null && k.equals(ek))))
+ return e;
+ if (eh < 0)
+ return e.find(h, k);
+ } while ((e = e.next) != null);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * A place-holder node used in computeIfAbsent and compute
+ */
+ static final class ReservationNode<K,V> extends Node<K,V> {
+ ReservationNode() {
+ super(RESERVED, null, null, null);
+ }
+
+ Node<K,V> find(int h, Object k) {
+ return null;
+ }
+ }
+
+ /* ---------------- Table Initialization and Resizing -------------- */
+
+ /**
+ * Initializes table, using the size recorded in sizeCtl.
+ */
+ private final Node<K,V>[] initTable() {
+ Node<K,V>[] tab; int sc;
+ while ((tab = table) == null || tab.length == 0) {
+ if ((sc = sizeCtl) < 0)
+ Thread.yield(); // lost initialization race; just spin
+ else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
+ try {
+ if ((tab = table) == null || tab.length == 0) {
+ int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
+ @SuppressWarnings({"rawtypes","unchecked"})
+ Node<K,V>[] nt = (Node<K,V>[])new Node[n];
+ table = tab = nt;
+ sc = n - (n >>> 2);
+ }
+ } finally {
+ sizeCtl = sc;
+ }
+ break;
+ }
+ }
+ return tab;
+ }
+
+ /**
+ * Adds to count, and if table is too small and not already
+ * resizing, initiates transfer. If already resizing, helps
+ * perform transfer if work is available. Rechecks occupancy
+ * after a transfer to see if another resize is already needed
+ * because resizings are lagging additions.
+ *
+ * @param x the count to add
+ * @param check if <0, don't check resize, if <= 1 only check if uncontended
+ */
+ private final void addCount(long x, int check) {
+ CounterCell[] as; long b, s;
+ if ((as = counterCells) != null ||
+ !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
+ CounterHashCode hc; CounterCell a; long v; int m;
+ boolean uncontended = true;
+ if ((hc = threadCounterHashCode.get()) == null ||
+ as == null || (m = as.length - 1) < 0 ||
+ (a = as[m & hc.code]) == null ||
+ !(uncontended =
+ U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
+ fullAddCount(x, hc, uncontended);
+ return;
+ }
+ if (check <= 1)
+ return;
+ s = sumCount();
+ }
+ if (check >= 0) {
+ Node<K,V>[] tab, nt; int sc;
+ while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
+ tab.length < MAXIMUM_CAPACITY) {
+ if (sc < 0) {
+ if (sc == -1 || transferIndex <= transferOrigin ||
+ (nt = nextTable) == null)
+ break;
+ if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1))
+ transfer(tab, nt);
+ }
+ else if (U.compareAndSwapInt(this, SIZECTL, sc, -2))
+ transfer(tab, null);
+ s = sumCount();
+ }
+ }
+ }
+
+ /**
+ * Helps transfer if a resize is in progress.
+ */
+ final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
+ Node<K,V>[] nextTab; int sc;
+ if ((f instanceof ForwardingNode) &&
+ (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
+ if (nextTab == nextTable && tab == table &&
+ transferIndex > transferOrigin && (sc = sizeCtl) < -1 &&
+ U.compareAndSwapInt(this, SIZECTL, sc, sc - 1))
+ transfer(tab, nextTab);
+ return nextTab;
+ }
+ return table;
+ }
+
+ /**
+ * Tries to presize table to accommodate the given number of elements.
+ *
+ * @param size number of elements (doesn't need to be perfectly accurate)
+ */
+ private final void tryPresize(int size) {
+ int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
+ tableSizeFor(size + (size >>> 1) + 1);
+ int sc;
+ while ((sc = sizeCtl) >= 0) {
+ Node<K,V>[] tab = table; int n;
+ if (tab == null || (n = tab.length) == 0) {
+ n = (sc > c) ? sc : c;
+ if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
+ try {
+ if (table == tab) {
+ @SuppressWarnings({"rawtypes","unchecked"})
+ Node<K,V>[] nt = (Node<K,V>[])new Node[n];
+ table = nt;
+ sc = n - (n >>> 2);
+ }
+ } finally {
+ sizeCtl = sc;
+ }
+ }
+ }
+ else if (c <= sc || n >= MAXIMUM_CAPACITY)
+ break;
+ else if (tab == table &&
+ U.compareAndSwapInt(this, SIZECTL, sc, -2))
+ transfer(tab, null);
+ }
+ }
+
+ /**
+ * Moves and/or copies the nodes in each bin to new table. See
+ * above for explanation.
+ */
+ private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
+ int n = tab.length, stride;
+ if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
+ stride = MIN_TRANSFER_STRIDE; // subdivide range
+ if (nextTab == null) { // initiating
+ try {
+ @SuppressWarnings({"rawtypes","unchecked"})
+ Node<K,V>[] nt = (Node<K,V>[])new Node[n << 1];
+ nextTab = nt;
+ } catch (Throwable ex) { // try to cope with OOME
+ sizeCtl = Integer.MAX_VALUE;
+ return;
+ }
+ nextTable = nextTab;
+ transferOrigin = n;
+ transferIndex = n;
+ ForwardingNode<K,V> rev = new ForwardingNode<K,V>(tab);
+ for (int k = n; k > 0;) { // progressively reveal ready slots
+ int nextk = (k > stride) ? k - stride : 0;
+ for (int m = nextk; m < k; ++m)
+ nextTab[m] = rev;
+ for (int m = n + nextk; m < n + k; ++m)
+ nextTab[m] = rev;
+ U.putOrderedInt(this, TRANSFERORIGIN, k = nextk);
+ }
+ }
+ int nextn = nextTab.length;
+ ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
+ boolean advance = true;
+ for (int i = 0, bound = 0;;) {
+ int nextIndex, nextBound, fh; Node<K,V> f;
+ while (advance) {
+ if (--i >= bound)
+ advance = false;
+ else if ((nextIndex = transferIndex) <= transferOrigin) {
+ i = -1;
+ advance = false;
+ }
+ else if (U.compareAndSwapInt
+ (this, TRANSFERINDEX, nextIndex,
+ nextBound = (nextIndex > stride ?
+ nextIndex - stride : 0))) {
+ bound = nextBound;
+ i = nextIndex - 1;
+ advance = false;
+ }
+ }
+ if (i < 0 || i >= n || i + n >= nextn) {
+ for (int sc;;) {
+ if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, ++sc)) {
+ if (sc == -1) {
+ nextTable = null;
+ table = nextTab;
+ sizeCtl = (n << 1) - (n >>> 1);
+ }
+ return;
+ }
+ }
+ }
+ else if ((f = tabAt(tab, i)) == null) {
+ if (casTabAt(tab, i, null, fwd)) {
+ setTabAt(nextTab, i, null);
+ setTabAt(nextTab, i + n, null);
+ advance = true;
+ }
+ }
+ else if ((fh = f.hash) == MOVED)
+ advance = true; // already processed
+ else {
+ synchronized (f) {
+ if (tabAt(tab, i) == f) {
+ Node<K,V> ln, hn;
+ if (fh >= 0) {
+ int runBit = fh & n;
+ Node<K,V> lastRun = f;
+ for (Node<K,V> p = f.next; p != null; p = p.next) {
+ int b = p.hash & n;
+ if (b != runBit) {
+ runBit = b;
+ lastRun = p;
+ }
+ }
+ if (runBit == 0) {
+ ln = lastRun;
+ hn = null;
+ }
+ else {
+ hn = lastRun;
+ ln = null;
+ }
+ for (Node<K,V> p = f; p != lastRun; p = p.next) {
+ int ph = p.hash; K pk = p.key; V pv = p.val;
+ if ((ph & n) == 0)
+ ln = new Node<K,V>(ph, pk, pv, ln);
+ else
+ hn = new Node<K,V>(ph, pk, pv, hn);
+ }
+ }
+ else if (f instanceof TreeBin) {
+ TreeBin<K,V> t = (TreeBin<K,V>)f;
+ TreeNode<K,V> lo = null, loTail = null;
+ TreeNode<K,V> hi = null, hiTail = null;
+ int lc = 0, hc = 0;
+ for (Node<K,V> e = t.first; e != null; e = e.next) {
+ int h = e.hash;
+ TreeNode<K,V> p = new TreeNode<K,V>
+ (h, e.key, e.val, null, null);
+ if ((h & n) == 0) {
+ if ((p.prev = loTail) == null)
+ lo = p;
+ else
+ loTail.next = p;
+ loTail = p;
+ ++lc;
+ }
+ else {
+ if ((p.prev = hiTail) == null)
+ hi = p;
+ else
+ hiTail.next = p;
+ hiTail = p;
+ ++hc;
+ }
+ }
+ ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
+ (hc != 0) ? new TreeBin<K,V>(lo) : t;
+ hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
+ (lc != 0) ? new TreeBin<K,V>(hi) : t;
+ }
+ else
+ ln = hn = null;
+ setTabAt(nextTab, i, ln);
+ setTabAt(nextTab, i + n, hn);
+ setTabAt(tab, i, fwd);
+ advance = true;
+ }
+ }
+ }
+ }
+ }
+
+ /* ---------------- Conversion from/to TreeBins -------------- */
+
+ /**
+ * Replaces all linked nodes in bin at given index unless table is
+ * too small, in which case resizes instead.
+ */
+ private final void treeifyBin(Node<K,V>[] tab, int index) {
+ Node<K,V> b; int n, sc;
+ if (tab != null) {
+ if ((n = tab.length) < MIN_TREEIFY_CAPACITY) {
+ if (tab == table && (sc = sizeCtl) >= 0 &&
+ U.compareAndSwapInt(this, SIZECTL, sc, -2))
+ transfer(tab, null);
+ }
+ else if ((b = tabAt(tab, index)) != null) {
+ synchronized (b) {
+ if (tabAt(tab, index) == b) {
+ TreeNode<K,V> hd = null, tl = null;
+ for (Node<K,V> e = b; e != null; e = e.next) {
+ TreeNode<K,V> p =
+ new TreeNode<K,V>(e.hash, e.key, e.val,
+ null, null);
+ if ((p.prev = tl) == null)
+ hd = p;
+ else
+ tl.next = p;
+ tl = p;
+ }
+ setTabAt(tab, index, new TreeBin<K,V>(hd));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a list on non-TreeNodes replacing those in given list.
+ */
+ static <K,V> Node<K,V> untreeify(Node<K,V> b) {
+ Node<K,V> hd = null, tl = null;
+ for (Node<K,V> q = b; q != null; q = q.next) {
+ Node<K,V> p = new Node<K,V>(q.hash, q.key, q.val, null);
+ if (tl == null)
+ hd = p;
+ else
+ tl.next = p;
+ tl = p;
+ }
+ return hd;
+ }
+
+ /* ---------------- TreeNodes -------------- */
+
+ /**
+ * Nodes for use in TreeBins
+ */
+ static final class TreeNode<K,V> extends Node<K,V> {
+ TreeNode<K,V> parent; // red-black tree links
+ TreeNode<K,V> left;
+ TreeNode<K,V> right;
+ TreeNode<K,V> prev; // needed to unlink next upon deletion
+ boolean red;
+
+ TreeNode(int hash, K key, V val, Node<K,V> next,
+ TreeNode<K,V> parent) {
+ super(hash, key, val, next);
+ this.parent = parent;
+ }
+
+ Node<K,V> find(int h, Object k) {
+ return findTreeNode(h, k, null);
}
/**
- * Sets nextEntry to first node of next non-empty table
- * (in backwards order, to simplify checks).
+ * Returns the TreeNode (or null if not found) for the given key
+ * starting at given root.
*/
- final void advance() {
- for (;;) {
- if (nextTableIndex >= 0) {
- if ((nextEntry = entryAt(currentTable,
- nextTableIndex--)) != null)
+ final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
+ if (k != null) {
+ TreeNode<K,V> p = this;
+ do {
+ int ph, dir; K pk; TreeNode<K,V> q;
+ TreeNode<K,V> pl = p.left, pr = p.right;
+ if ((ph = p.hash) > h)
+ p = pl;
+ else if (ph < h)
+ p = pr;
+ else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
+ return p;
+ else if (pl == null && pr == null)
break;
+ else if ((kc != null ||
+ (kc = comparableClassFor(k)) != null) &&
+ (dir = compareComparables(kc, k, pk)) != 0)
+ p = (dir < 0) ? pl : pr;
+ else if (pl == null)
+ p = pr;
+ else if (pr == null ||
+ (q = pr.findTreeNode(h, k, kc)) == null)
+ p = pl;
+ else
+ return q;
+ } while (p != null);
+ }
+ return null;
+ }
+ }
+
+ /* ---------------- TreeBins -------------- */
+
+ /**
+ * TreeNodes used at the heads of bins. TreeBins do not hold user
+ * keys or values, but instead point to list of TreeNodes and
+ * their root. They also maintain a parasitic read-write lock
+ * forcing writers (who hold bin lock) to wait for readers (who do
+ * not) to complete before tree restructuring operations.
+ */
+ static final class TreeBin<K,V> extends Node<K,V> {
+ TreeNode<K,V> root;
+ volatile TreeNode<K,V> first;
+ volatile Thread waiter;
+ volatile int lockState;
+ // values for lockState
+ static final int WRITER = 1; // set while holding write lock
+ static final int WAITER = 2; // set when waiting for write lock
+ static final int READER = 4; // increment value for setting read lock
+
+ /**
+ * Creates bin with initial set of nodes headed by b.
+ */
+ TreeBin(TreeNode<K,V> b) {
+ super(TREEBIN, null, null, null);
+ this.first = b;
+ TreeNode<K,V> r = null;
+ for (TreeNode<K,V> x = b, next; x != null; x = next) {
+ next = (TreeNode<K,V>)x.next;
+ x.left = x.right = null;
+ if (r == null) {
+ x.parent = null;
+ x.red = false;
+ r = x;
}
- else if (nextSegmentIndex >= 0) {
- Segment<K,V> seg = segmentAt(segments, nextSegmentIndex--);
- if (seg != null && (currentTable = seg.table) != null)
- nextTableIndex = currentTable.length - 1;
+ else {
+ Object key = x.key;
+ int hash = x.hash;
+ Class<?> kc = null;
+ for (TreeNode<K,V> p = r;;) {
+ int dir, ph;
+ if ((ph = p.hash) > hash)
+ dir = -1;
+ else if (ph < hash)
+ dir = 1;
+ else if ((kc != null ||
+ (kc = comparableClassFor(key)) != null))
+ dir = compareComparables(kc, key, p.key);
+ else
+ dir = 0;
+ TreeNode<K,V> xp = p;
+ if ((p = (dir <= 0) ? p.left : p.right) == null) {
+ x.parent = xp;
+ if (dir <= 0)
+ xp.left = x;
+ else
+ xp.right = x;
+ r = balanceInsertion(r, x);
+ break;
+ }
+ }
}
- else
+ }
+ this.root = r;
+ }
+
+ /**
+ * Acquires write lock for tree restructuring.
+ */
+ private final void lockRoot() {
+ if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
+ contendedLock(); // offload to separate method
+ }
+
+ /**
+ * Releases write lock for tree restructuring.
+ */
+ private final void unlockRoot() {
+ lockState = 0;
+ }
+
+ /**
+ * Possibly blocks awaiting root lock.
+ */
+ private final void contendedLock() {
+ boolean waiting = false;
+ for (int s;;) {
+ if (((s = lockState) & WRITER) == 0) {
+ if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {
+ if (waiting)
+ waiter = null;
+ return;
+ }
+ }
+ else if ((s & WAITER) == 0) {
+ if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) {
+ waiting = true;
+ waiter = Thread.currentThread();
+ }
+ }
+ else if (waiting)
+ LockSupport.park(this);
+ }
+ }
+
+ /**
+ * Returns matching node or null if none. Tries to search
+ * using tree comparisons from root, but continues linear
+ * search when lock not available.
+ */
+ final Node<K,V> find(int h, Object k) {
+ if (k != null) {
+ for (Node<K,V> e = first; e != null; e = e.next) {
+ int s; K ek;
+ if (((s = lockState) & (WAITER|WRITER)) != 0) {
+ if (e.hash == h &&
+ ((ek = e.key) == k || (ek != null && k.equals(ek))))
+ return e;
+ }
+ else if (U.compareAndSwapInt(this, LOCKSTATE, s,
+ s + READER)) {
+ TreeNode<K,V> r, p;
+ try {
+ p = ((r = root) == null ? null :
+ r.findTreeNode(h, k, null));
+ } finally {
+
+ Thread w;
+ int ls;
+ do {} while (!U.compareAndSwapInt
+ (this, LOCKSTATE,
+ ls = lockState, ls - READER));
+ if (ls == (READER|WAITER) && (w = waiter) != null)
+ LockSupport.unpark(w);
+ }
+ return p;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Finds or adds a node.
+ * @return null if added
+ */
+ final TreeNode<K,V> putTreeVal(int h, K k, V v) {
+ Class<?> kc = null;
+ for (TreeNode<K,V> p = root;;) {
+ int dir, ph; K pk; TreeNode<K,V> q, pr;
+ if (p == null) {
+ first = root = new TreeNode<K,V>(h, k, v, null, null);
+ break;
+ }
+ else if ((ph = p.hash) > h)
+ dir = -1;
+ else if (ph < h)
+ dir = 1;
+ else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
+ return p;
+ else if ((kc == null &&
+ (kc = comparableClassFor(k)) == null) ||
+ (dir = compareComparables(kc, k, pk)) == 0) {
+ if (p.left == null)
+ dir = 1;
+ else if ((pr = p.right) == null ||
+ (q = pr.findTreeNode(h, k, kc)) == null)
+ dir = -1;
+ else
+ return q;
+ }
+ TreeNode<K,V> xp = p;
+ if ((p = (dir < 0) ? p.left : p.right) == null) {
+ TreeNode<K,V> x, f = first;
+ first = x = new TreeNode<K,V>(h, k, v, f, xp);
+ if (f != null)
+ f.prev = x;
+ if (dir < 0)
+ xp.left = x;
+ else
+ xp.right = x;
+ if (!xp.red)
+ x.red = true;
+ else {
+ lockRoot();
+ try {
+ root = balanceInsertion(root, x);
+ } finally {
+ unlockRoot();
+ }
+ }
break;
+ }
}
+ assert checkInvariants(root);
+ return null;
}
- final HashEntry<K,V> nextEntry() {
- HashEntry<K,V> e = nextEntry;
- if (e == null)
- throw new NoSuchElementException();
- lastReturned = e; // cannot assign until after null check
- if ((nextEntry = e.next) == null)
- advance();
- return e;
+ /**
+ * Removes the given node, that must be present before this
+ * call. This is messier than typical red-black deletion code
+ * because we cannot swap the contents of an interior node
+ * with a leaf successor that is pinned by "next" pointers
+ * that are accessible independently of lock. So instead we
+ * swap the tree linkages.
+ *
+ * @return true if now too small, so should be untreeified
+ */
+ final boolean removeTreeNode(TreeNode<K,V> p) {
+ TreeNode<K,V> next = (TreeNode<K,V>)p.next;
+ TreeNode<K,V> pred = p.prev; // unlink traversal pointers
+ TreeNode<K,V> r, rl;
+ if (pred == null)
+ first = next;
+ else
+ pred.next = next;
+ if (next != null)
+ next.prev = pred;
+ if (first == null) {
+ root = null;
+ return true;
+ }
+ if ((r = root) == null || r.right == null || // too small
+ (rl = r.left) == null || rl.left == null)
+ return true;
+ lockRoot();
+ try {
+ TreeNode<K,V> replacement;
+ TreeNode<K,V> pl = p.left;
+ TreeNode<K,V> pr = p.right;
+ if (pl != null && pr != null) {
+ TreeNode<K,V> s = pr, sl;
+ while ((sl = s.left) != null) // find successor
+ s = sl;
+ boolean c = s.red; s.red = p.red; p.red = c; // swap colors
+ TreeNode<K,V> sr = s.right;
+ TreeNode<K,V> pp = p.parent;
+ if (s == pr) { // p was s's direct parent
+ p.parent = s;
+ s.right = p;
+ }
+ else {
+ TreeNode<K,V> sp = s.parent;
+ if ((p.parent = sp) != null) {
+ if (s == sp.left)
+ sp.left = p;
+ else
+ sp.right = p;
+ }
+ if ((s.right = pr) != null)
+ pr.parent = s;
+ }
+ p.left = null;
+ if ((p.right = sr) != null)
+ sr.parent = p;
+ if ((s.left = pl) != null)
+ pl.parent = s;
+ if ((s.parent = pp) == null)
+ r = s;
+ else if (p == pp.left)
+ pp.left = s;
+ else
+ pp.right = s;
+ if (sr != null)
+ replacement = sr;
+ else
+ replacement = p;
+ }
+ else if (pl != null)
+ replacement = pl;
+ else if (pr != null)
+ replacement = pr;
+ else
+ replacement = p;
+ if (replacement != p) {
+ TreeNode<K,V> pp = replacement.parent = p.parent;
+ if (pp == null)
+ r = replacement;
+ else if (p == pp.left)
+ pp.left = replacement;
+ else
+ pp.right = replacement;
+ p.left = p.right = p.parent = null;
+ }
+
+ root = (p.red) ? r : balanceDeletion(r, replacement);
+
+ if (p == replacement) { // detach pointers
+ TreeNode<K,V> pp;
+ if ((pp = p.parent) != null) {
+ if (p == pp.left)
+ pp.left = null;
+ else if (p == pp.right)
+ pp.right = null;
+ p.parent = null;
+ }
+ }
+ } finally {
+ unlockRoot();
+ }
+ assert checkInvariants(root);
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ // Red-black tree methods, all adapted from CLR
+
+ static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
+ TreeNode<K,V> p) {
+ TreeNode<K,V> r, pp, rl;
+ if (p != null && (r = p.right) != null) {
+ if ((rl = p.right = r.left) != null)
+ rl.parent = p;
+ if ((pp = r.parent = p.parent) == null)
+ (root = r).red = false;
+ else if (pp.left == p)
+ pp.left = r;
+ else
+ pp.right = r;
+ r.left = p;
+ p.parent = r;
+ }
+ return root;
+ }
+
+ static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
+ TreeNode<K,V> p) {
+ TreeNode<K,V> l, pp, lr;
+ if (p != null && (l = p.left) != null) {
+ if ((lr = p.left = l.right) != null)
+ lr.parent = p;
+ if ((pp = l.parent = p.parent) == null)
+ (root = l).red = false;
+ else if (pp.right == p)
+ pp.right = l;
+ else
+ pp.left = l;
+ l.right = p;
+ p.parent = l;
+ }
+ return root;
}
- public final boolean hasNext() { return nextEntry != null; }
- public final boolean hasMoreElements() { return nextEntry != null; }
+ static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
+ TreeNode<K,V> x) {
+ x.red = true;
+ for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
+ if ((xp = x.parent) == null) {
+ x.red = false;
+ return x;
+ }
+ else if (!xp.red || (xpp = xp.parent) == null)
+ return root;
+ if (xp == (xppl = xpp.left)) {
+ if ((xppr = xpp.right) != null && xppr.red) {
+ xppr.red = false;
+ xp.red = false;
+ xpp.red = true;
+ x = xpp;
+ }
+ else {
+ if (x == xp.right) {
+ root = rotateLeft(root, x = xp);
+ xpp = (xp = x.parent) == null ? null : xp.parent;
+ }
+ if (xp != null) {
+ xp.red = false;
+ if (xpp != null) {
+ xpp.red = true;
+ root = rotateRight(root, xpp);
+ }
+ }
+ }
+ }
+ else {
+ if (xppl != null && xppl.red) {
+ xppl.red = false;
+ xp.red = false;
+ xpp.red = true;
+ x = xpp;
+ }
+ else {
+ if (x == xp.left) {
+ root = rotateRight(root, x = xp);
+ xpp = (xp = x.parent) == null ? null : xp.parent;
+ }
+ if (xp != null) {
+ xp.red = false;
+ if (xpp != null) {
+ xpp.red = true;
+ root = rotateLeft(root, xpp);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
+ TreeNode<K,V> x) {
+ for (TreeNode<K,V> xp, xpl, xpr;;) {
+ if (x == null || x == root)
+ return root;
+ else if ((xp = x.parent) == null) {
+ x.red = false;
+ return x;
+ }
+ else if (x.red) {
+ x.red = false;
+ return root;
+ }
+ else if ((xpl = xp.left) == x) {
+ if ((xpr = xp.right) != null && xpr.red) {
+ xpr.red = false;
+ xp.red = true;
+ root = rotateLeft(root, xp);
+ xpr = (xp = x.parent) == null ? null : xp.right;
+ }
+ if (xpr == null)
+ x = xp;
+ else {
+ TreeNode<K,V> sl = xpr.left, sr = xpr.right;
+ if ((sr == null || !sr.red) &&
+ (sl == null || !sl.red)) {
+ xpr.red = true;
+ x = xp;
+ }
+ else {
+ if (sr == null || !sr.red) {
+ if (sl != null)
+ sl.red = false;
+ xpr.red = true;
+ root = rotateRight(root, xpr);
+ xpr = (xp = x.parent) == null ?
+ null : xp.right;
+ }
+ if (xpr != null) {
+ xpr.red = (xp == null) ? false : xp.red;
+ if ((sr = xpr.right) != null)
+ sr.red = false;
+ }
+ if (xp != null) {
+ xp.red = false;
+ root = rotateLeft(root, xp);
+ }
+ x = root;
+ }
+ }
+ }
+ else { // symmetric
+ if (xpl != null && xpl.red) {
+ xpl.red = false;
+ xp.red = true;
+ root = rotateRight(root, xp);
+ xpl = (xp = x.parent) == null ? null : xp.left;
+ }
+ if (xpl == null)
+ x = xp;
+ else {
+ TreeNode<K,V> sl = xpl.left, sr = xpl.right;
+ if ((sl == null || !sl.red) &&
+ (sr == null || !sr.red)) {
+ xpl.red = true;
+ x = xp;
+ }
+ else {
+ if (sl == null || !sl.red) {
+ if (sr != null)
+ sr.red = false;
+ xpl.red = true;
+ root = rotateLeft(root, xpl);
+ xpl = (xp = x.parent) == null ?
+ null : xp.left;
+ }
+ if (xpl != null) {
+ xpl.red = (xp == null) ? false : xp.red;
+ if ((sl = xpl.left) != null)
+ sl.red = false;
+ }
+ if (xp != null) {
+ xp.red = false;
+ root = rotateRight(root, xp);
+ }
+ x = root;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Recursive invariant check
+ */
+ static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
+ TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
+ tb = t.prev, tn = (TreeNode<K,V>)t.next;
+ if (tb != null && tb.next != t)
+ return false;
+ if (tn != null && tn.prev != t)
+ return false;
+ if (tp != null && t != tp.left && t != tp.right)
+ return false;
+ if (tl != null && (tl.parent != t || tl.hash > t.hash))
+ return false;
+ if (tr != null && (tr.parent != t || tr.hash < t.hash))
+ return false;
+ if (t.red && tl != null && tl.red && tr != null && tr.red)
+ return false;
+ if (tl != null && !checkInvariants(tl))
+ return false;
+ if (tr != null && !checkInvariants(tr))
+ return false;
+ return true;
+ }
+
+ private static final sun.misc.Unsafe U;
+ private static final long LOCKSTATE;
+ static {
+ try {
+ U = sun.misc.Unsafe.getUnsafe();
+ Class<?> k = TreeBin.class;
+ LOCKSTATE = U.objectFieldOffset
+ (k.getDeclaredField("lockState"));
+ } catch (Exception e) {
+ throw new Error(e);
+ }
+ }
+ }
+
+ /* ----------------Table Traversal -------------- */
+
+ /**
+ * Encapsulates traversal for methods such as containsValue; also
+ * serves as a base class for other iterators.
+ *
+ * Method advance visits once each still-valid node that was
+ * reachable upon iterator construction. It might miss some that
+ * were added to a bin after the bin was visited, which is OK wrt
+ * consistency guarantees. Maintaining this property in the face
+ * of possible ongoing resizes requires a fair amount of
+ * bookkeeping state that is difficult to optimize away amidst
+ * volatile accesses. Even so, traversal maintains reasonable
+ * throughput.
+ *
+ * Normally, iteration proceeds bin-by-bin traversing lists.
+ * However, if the table has been resized, then all future steps
+ * must traverse both the bin at the current index as well as at
+ * (index + baseSize); and so on for further resizings. To
+ * paranoically cope with potential sharing by users of iterators
+ * across threads, iteration terminates if a bounds checks fails
+ * for a table read.
+ */
+ static class Traverser<K,V> {
+ Node<K,V>[] tab; // current table; updated if resized
+ Node<K,V> next; // the next entry to use
+ int index; // index of bin to use next
+ int baseIndex; // current index of initial table
+ int baseLimit; // index bound for initial table
+ final int baseSize; // initial table size
+
+ Traverser(Node<K,V>[] tab, int size, int index, int limit) {
+ this.tab = tab;
+ this.baseSize = size;
+ this.baseIndex = this.index = index;
+ this.baseLimit = limit;
+ this.next = null;
+ }
+
+ /**
+ * Advances if possible, returning next valid node, or null if none.
+ */
+ final Node<K,V> advance() {
+ Node<K,V> e;
+ if ((e = next) != null)
+ e = e.next;
+ for (;;) {
+ Node<K,V>[] t; int i, n; K ek; // must use locals in checks
+ if (e != null)
+ return next = e;
+ if (baseIndex >= baseLimit || (t = tab) == null ||
+ (n = t.length) <= (i = index) || i < 0)
+ return next = null;
+ if ((e = tabAt(t, index)) != null && e.hash < 0) {
+ if (e instanceof ForwardingNode) {
+ tab = ((ForwardingNode<K,V>)e).nextTable;
+ e = null;
+ continue;
+ }
+ else if (e instanceof TreeBin)
+ e = ((TreeBin<K,V>)e).first;
+ else
+ e = null;
+ }
+ if ((index += baseSize) >= n)
+ index = ++baseIndex; // visit upper slots if present
+ }
+ }
+ }
+
+ /**
+ * Base of key, value, and entry Iterators. Adds fields to
+ * Traverser to support iterator.remove.
+ */
+ static class BaseIterator<K,V> extends Traverser<K,V> {
+ final ConcurrentHashMap<K,V> map;
+ Node<K,V> lastReturned;
+ BaseIterator(Node<K,V>[] tab, int size, int index, int limit,
+ ConcurrentHashMap<K,V> map) {
+ super(tab, size, index, limit);
+ this.map = map;
+ advance();
+ }
+
+ public final boolean hasNext() { return next != null; }
+ public final boolean hasMoreElements() { return next != null; }
public final void remove() {
- if (lastReturned == null)
+ Node<K,V> p;
+ if ((p = lastReturned) == null)
throw new IllegalStateException();
- ConcurrentHashMap.this.remove(lastReturned.key);
lastReturned = null;
+ map.replaceNode(p.key, null, null);
+ }
+ }
+
+ static final class KeyIterator<K,V> extends BaseIterator<K,V>
+ implements Iterator<K>, Enumeration<K> {
+ KeyIterator(Node<K,V>[] tab, int index, int size, int limit,
+ ConcurrentHashMap<K,V> map) {
+ super(tab, index, size, limit, map);
+ }
+
+ public final K next() {
+ Node<K,V> p;
+ if ((p = next) == null)
+ throw new NoSuchElementException();
+ K k = p.key;
+ lastReturned = p;
+ advance();
+ return k;
}
+
+ public final K nextElement() { return next(); }
}
- final class KeyIterator
- extends HashIterator
- implements Iterator<K>, Enumeration<K>
- {
- public final K next() { return super.nextEntry().key; }
- public final K nextElement() { return super.nextEntry().key; }
+ static final class ValueIterator<K,V> extends BaseIterator<K,V>
+ implements Iterator<V>, Enumeration<V> {
+ ValueIterator(Node<K,V>[] tab, int index, int size, int limit,
+ ConcurrentHashMap<K,V> map) {
+ super(tab, index, size, limit, map);
+ }
+
+ public final V next() {
+ Node<K,V> p;
+ if ((p = next) == null)
+ throw new NoSuchElementException();
+ V v = p.val;
+ lastReturned = p;
+ advance();
+ return v;
+ }
+
+ public final V nextElement() { return next(); }
}
- final class ValueIterator
- extends HashIterator
- implements Iterator<V>, Enumeration<V>
- {
- public final V next() { return super.nextEntry().value; }
- public final V nextElement() { return super.nextEntry().value; }
+ static final class EntryIterator<K,V> extends BaseIterator<K,V>
+ implements Iterator<Map.Entry<K,V>> {
+ EntryIterator(Node<K,V>[] tab, int index, int size, int limit,
+ ConcurrentHashMap<K,V> map) {
+ super(tab, index, size, limit, map);
+ }
+
+ public final Map.Entry<K,V> next() {
+ Node<K,V> p;
+ if ((p = next) == null)
+ throw new NoSuchElementException();
+ K k = p.key;
+ V v = p.val;
+ lastReturned = p;
+ advance();
+ return new MapEntry<K,V>(k, v, map);
+ }
}
/**
- * Custom Entry class used by EntryIterator.next(), that relays
- * setValue changes to the underlying map.
+ * Exported Entry for EntryIterator
*/
- final class WriteThroughEntry
- extends AbstractMap.SimpleEntry<K,V>
- {
- WriteThroughEntry(K k, V v) {
- super(k,v);
+ static final class MapEntry<K,V> implements Map.Entry<K,V> {
+ final K key; // non-null
+ V val; // non-null
+ final ConcurrentHashMap<K,V> map;
+ MapEntry(K key, V val, ConcurrentHashMap<K,V> map) {
+ this.key = key;
+ this.val = val;
+ this.map = map;
+ }
+ public K getKey() { return key; }
+ public V getValue() { return val; }
+ public int hashCode() { return key.hashCode() ^ val.hashCode(); }
+ public String toString() { return key + "=" + val; }
+
+ public boolean equals(Object o) {
+ Object k, v; Map.Entry<?,?> e;
+ return ((o instanceof Map.Entry) &&
+ (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
+ (v = e.getValue()) != null &&
+ (k == key || k.equals(key)) &&
+ (v == val || v.equals(val)));
}
/**
* Sets our entry's value and writes through to the map. The
- * value to return is somewhat arbitrary here. Since a
- * WriteThroughEntry does not necessarily track asynchronous
- * changes, the most recent "previous" value could be
- * different from what we return (or could even have been
- * removed in which case the put will re-establish). We do not
- * and cannot guarantee more.
+ * value to return is somewhat arbitrary here. Since we do not
+ * necessarily track asynchronous changes, the most recent
+ * "previous" value could be different from what we return (or
+ * could even have been removed, in which case the put will
+ * re-establish). We do not and cannot guarantee more.
*/
public V setValue(V value) {
if (value == null) throw new NullPointerException();
- V v = super.setValue(value);
- ConcurrentHashMap.this.put(getKey(), value);
+ V v = val;
+ val = value;
+ map.put(key, value);
return v;
}
}
- final class EntryIterator
- extends HashIterator
- implements Iterator<Entry<K,V>>
- {
- public Map.Entry<K,V> next() {
- HashEntry<K,V> e = super.nextEntry();
- return new WriteThroughEntry(e.key, e.value);
+ /* ----------------Views -------------- */
+
+ /**
+ * Base class for views.
+ *
+ */
+ abstract static class CollectionView<K,V,E>
+ implements Collection<E>, java.io.Serializable {
+ private static final long serialVersionUID = 7249069246763182397L;
+ final ConcurrentHashMap<K,V> map;
+ CollectionView(ConcurrentHashMap<K,V> map) { this.map = map; }
+
+ /**
+ * Returns the map backing this view.
+ *
+ * @return the map backing this view
+ */
+ public ConcurrentHashMap<K,V> getMap() { return map; }
+
+ /**
+ * Removes all of the elements from this view, by removing all
+ * the mappings from the map backing this view.
+ */
+ public final void clear() { map.clear(); }
+ public final int size() { return map.size(); }
+ public final boolean isEmpty() { return map.isEmpty(); }
+
+ // implementations below rely on concrete classes supplying these
+ // abstract methods
+ /**
+ * Returns a "weakly consistent" iterator that will never
+ * throw {@link ConcurrentModificationException}, and
+ * guarantees to traverse elements as they existed upon
+ * construction of the iterator, and may (but is not
+ * guaranteed to) reflect any modifications subsequent to
+ * construction.
+ */
+ public abstract Iterator<E> iterator();
+ public abstract boolean contains(Object o);
+ public abstract boolean remove(Object o);
+
+ private static final String oomeMsg = "Required array size too large";
+
+ public final Object[] toArray() {
+ long sz = map.mappingCount();
+ if (sz > MAX_ARRAY_SIZE)
+ throw new OutOfMemoryError(oomeMsg);
+ int n = (int)sz;
+ Object[] r = new Object[n];
+ int i = 0;
+ for (E e : this) {
+ if (i == n) {
+ if (n >= MAX_ARRAY_SIZE)
+ throw new OutOfMemoryError(oomeMsg);
+ if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1)
+ n = MAX_ARRAY_SIZE;
+ else
+ n += (n >>> 1) + 1;
+ r = Arrays.copyOf(r, n);
+ }
+ r[i++] = e;
+ }
+ return (i == n) ? r : Arrays.copyOf(r, i);
+ }
+
+ @SuppressWarnings("unchecked")
+ public final <T> T[] toArray(T[] a) {
+ long sz = map.mappingCount();
+ if (sz > MAX_ARRAY_SIZE)
+ throw new OutOfMemoryError(oomeMsg);
+ int m = (int)sz;
+ T[] r = (a.length >= m) ? a :
+ (T[])java.lang.reflect.Array
+ .newInstance(a.getClass().getComponentType(), m);
+ int n = r.length;
+ int i = 0;
+ for (E e : this) {
+ if (i == n) {
+ if (n >= MAX_ARRAY_SIZE)
+ throw new OutOfMemoryError(oomeMsg);
+ if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1)
+ n = MAX_ARRAY_SIZE;
+ else
+ n += (n >>> 1) + 1;
+ r = Arrays.copyOf(r, n);
+ }
+ r[i++] = (T)e;
+ }
+ if (a == r && i < n) {
+ r[i] = null; // null-terminate
+ return r;
+ }
+ return (i == n) ? r : Arrays.copyOf(r, i);
+ }
+
+ /**
+ * Returns a string representation of this collection.
+ * The string representation consists of the string representations
+ * of the collection's elements in the order they are returned by
+ * its iterator, enclosed in square brackets ({@code "[]"}).
+ * Adjacent elements are separated by the characters {@code ", "}
+ * (comma and space). Elements are converted to strings as by
+ * {@link String#valueOf(Object)}.
+ *
+ * @return a string representation of this collection
+ */
+ public final String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('[');
+ Iterator<E> it = iterator();
+ if (it.hasNext()) {
+ for (;;) {
+ Object e = it.next();
+ sb.append(e == this ? "(this Collection)" : e);
+ if (!it.hasNext())
+ break;
+ sb.append(',').append(' ');
+ }
+ }
+ return sb.append(']').toString();
}
+
+ public final boolean containsAll(Collection<?> c) {
+ if (c != this) {
+ for (Object e : c) {
+ if (e == null || !contains(e))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public final boolean removeAll(Collection<?> c) {
+ boolean modified = false;
+ for (Iterator<E> it = iterator(); it.hasNext();) {
+ if (c.contains(it.next())) {
+ it.remove();
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ public final boolean retainAll(Collection<?> c) {
+ boolean modified = false;
+ for (Iterator<E> it = iterator(); it.hasNext();) {
+ if (!c.contains(it.next())) {
+ it.remove();
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
}
- final class KeySet extends AbstractSet<K> {
- public Iterator<K> iterator() {
- return new KeyIterator();
+ /**
+ * A view of a ConcurrentHashMap as a {@link Set} of keys, in
+ * which additions may optionally be enabled by mapping to a
+ * common value. This class cannot be directly instantiated.
+ * See {@link #keySet() keySet()},
+ * {@link #keySet(Object) keySet(V)},
+ * {@link #newKeySet() newKeySet()},
+ * {@link #newKeySet(int) newKeySet(int)}.
+ *
+ * @since 1.8
+ *
+ * @hide
+ */
+ public static class KeySetView<K,V> extends CollectionView<K,V,K>
+ implements Set<K>, java.io.Serializable {
+ private static final long serialVersionUID = 7249069246763182397L;
+ private final V value;
+ KeySetView(ConcurrentHashMap<K,V> map, V value) { // non-public
+ super(map);
+ this.value = value;
}
- public int size() {
- return ConcurrentHashMap.this.size();
+
+ /**
+ * Returns the default mapped value for additions,
+ * or {@code null} if additions are not supported.
+ *
+ * @return the default mapped value for additions, or {@code null}
+ * if not supported
+ */
+ public V getMappedValue() { return value; }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the specified key is null
+ */
+ public boolean contains(Object o) { return map.containsKey(o); }
+
+ /**
+ * Removes the key from this map view, by removing the key (and its
+ * corresponding value) from the backing map. This method does
+ * nothing if the key is not in the map.
+ *
+ * @param o the key to be removed from the backing map
+ * @return {@code true} if the backing map contained the specified key
+ * @throws NullPointerException if the specified key is null
+ */
+ public boolean remove(Object o) { return map.remove(o) != null; }
+
+ /**
+ * @return an iterator over the keys of the backing map
+ */
+ public Iterator<K> iterator() {
+ Node<K,V>[] t;
+ ConcurrentHashMap<K,V> m = map;
+ int f = (t = m.table) == null ? 0 : t.length;
+ return new KeyIterator<K,V>(t, f, 0, f, m);
}
- public boolean isEmpty() {
- return ConcurrentHashMap.this.isEmpty();
+
+ /**
+ * Adds the specified key to this set view by mapping the key to
+ * the default mapped value in the backing map, if defined.
+ *
+ * @param e key to be added
+ * @return {@code true} if this set changed as a result of the call
+ * @throws NullPointerException if the specified key is null
+ * @throws UnsupportedOperationException if no default mapped value
+ * for additions was provided
+ */
+ public boolean add(K e) {
+ V v;
+ if ((v = value) == null)
+ throw new UnsupportedOperationException();
+ return map.putVal(e, v, true) == null;
}
- public boolean contains(Object o) {
- return ConcurrentHashMap.this.containsKey(o);
+
+ /**
+ * Adds all of the elements in the specified collection to this set,
+ * as if by calling {@link #add} on each one.
+ *
+ * @param c the elements to be inserted into this set
+ * @return {@code true} if this set changed as a result of the call
+ * @throws NullPointerException if the collection or any of its
+ * elements are {@code null}
+ * @throws UnsupportedOperationException if no default mapped value
+ * for additions was provided
+ */
+ public boolean addAll(Collection<? extends K> c) {
+ boolean added = false;
+ V v;
+ if ((v = value) == null)
+ throw new UnsupportedOperationException();
+ for (K e : c) {
+ if (map.putVal(e, v, true) == null)
+ added = true;
+ }
+ return added;
}
- public boolean remove(Object o) {
- return ConcurrentHashMap.this.remove(o) != null;
+
+ public int hashCode() {
+ int h = 0;
+ for (K e : this)
+ h += e.hashCode();
+ return h;
}
- public void clear() {
- ConcurrentHashMap.this.clear();
+
+ public boolean equals(Object o) {
+ Set<?> c;
+ return ((o instanceof Set) &&
+ ((c = (Set<?>)o) == this ||
+ (containsAll(c) && c.containsAll(this))));
}
+
}
- final class Values extends AbstractCollection<V> {
- public Iterator<V> iterator() {
- return new ValueIterator();
+ /**
+ * A view of a ConcurrentHashMap as a {@link Collection} of
+ * values, in which additions are disabled. This class cannot be
+ * directly instantiated. See {@link #values()}.
+ */
+ static final class ValuesView<K,V> extends CollectionView<K,V,V>
+ implements Collection<V>, java.io.Serializable {
+ private static final long serialVersionUID = 2249069246763182397L;
+ ValuesView(ConcurrentHashMap<K,V> map) { super(map); }
+ public final boolean contains(Object o) {
+ return map.containsValue(o);
}
- public int size() {
- return ConcurrentHashMap.this.size();
+
+ public final boolean remove(Object o) {
+ if (o != null) {
+ for (Iterator<V> it = iterator(); it.hasNext();) {
+ if (o.equals(it.next())) {
+ it.remove();
+ return true;
+ }
+ }
+ }
+ return false;
}
- public boolean isEmpty() {
- return ConcurrentHashMap.this.isEmpty();
+
+ public final Iterator<V> iterator() {
+ ConcurrentHashMap<K,V> m = map;
+ Node<K,V>[] t;
+ int f = (t = m.table) == null ? 0 : t.length;
+ return new ValueIterator<K,V>(t, f, 0, f, m);
}
- public boolean contains(Object o) {
- return ConcurrentHashMap.this.containsValue(o);
+
+ public final boolean add(V e) {
+ throw new UnsupportedOperationException();
}
- public void clear() {
- ConcurrentHashMap.this.clear();
+ public final boolean addAll(Collection<? extends V> c) {
+ throw new UnsupportedOperationException();
}
+
}
- final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
- public Iterator<Map.Entry<K,V>> iterator() {
- return new EntryIterator();
- }
+ /**
+ * A view of a ConcurrentHashMap as a {@link Set} of (key, value)
+ * entries. This class cannot be directly instantiated. See
+ * {@link #entrySet()}.
+ */
+ static final class EntrySetView<K,V> extends CollectionView<K,V,Map.Entry<K,V>>
+ implements Set<Map.Entry<K,V>>, java.io.Serializable {
+ private static final long serialVersionUID = 2249069246763182397L;
+ EntrySetView(ConcurrentHashMap<K,V> map) { super(map); }
+
public boolean contains(Object o) {
- if (!(o instanceof Map.Entry))
- return false;
- Map.Entry<?,?> e = (Map.Entry<?,?>)o;
- V v = ConcurrentHashMap.this.get(e.getKey());
- return v != null && v.equals(e.getValue());
+ Object k, v, r; Map.Entry<?,?> e;
+ return ((o instanceof Map.Entry) &&
+ (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
+ (r = map.get(k)) != null &&
+ (v = e.getValue()) != null &&
+ (v == r || v.equals(r)));
}
+
public boolean remove(Object o) {
- if (!(o instanceof Map.Entry))
- return false;
- Map.Entry<?,?> e = (Map.Entry<?,?>)o;
- return ConcurrentHashMap.this.remove(e.getKey(), e.getValue());
+ Object k, v; Map.Entry<?,?> e;
+ return ((o instanceof Map.Entry) &&
+ (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
+ (v = e.getValue()) != null &&
+ map.remove(k, v));
}
- public int size() {
- return ConcurrentHashMap.this.size();
+
+ /**
+ * @return an iterator over the entries of the backing map
+ */
+ public Iterator<Map.Entry<K,V>> iterator() {
+ ConcurrentHashMap<K,V> m = map;
+ Node<K,V>[] t;
+ int f = (t = m.table) == null ? 0 : t.length;
+ return new EntryIterator<K,V>(t, f, 0, f, m);
}
- public boolean isEmpty() {
- return ConcurrentHashMap.this.isEmpty();
+
+ public boolean add(Entry<K,V> e) {
+ return map.putVal(e.getKey(), e.getValue(), false) == null;
}
- public void clear() {
- ConcurrentHashMap.this.clear();
+
+ public boolean addAll(Collection<? extends Entry<K,V>> c) {
+ boolean added = false;
+ for (Entry<K,V> e : c) {
+ if (add(e))
+ added = true;
+ }
+ return added;
}
+
+ public final int hashCode() {
+ int h = 0;
+ Node<K,V>[] t;
+ if ((t = map.table) != null) {
+ Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
+ for (Node<K,V> p; (p = it.advance()) != null; ) {
+ h += p.hashCode();
+ }
+ }
+ return h;
+ }
+
+ public final boolean equals(Object o) {
+ Set<?> c;
+ return ((o instanceof Set) &&
+ ((c = (Set<?>)o) == this ||
+ (containsAll(c) && c.containsAll(this))));
+ }
+
}
- /* ---------------- Serialization Support -------------- */
+
+ /* ---------------- Counters -------------- */
+
+ // Adapted from LongAdder and Striped64.
+ // See their internal docs for explanation.
+
+ // A padded cell for distributing counts
+ static final class CounterCell {
+ volatile long p0, p1, p2, p3, p4, p5, p6;
+ volatile long value;
+ volatile long q0, q1, q2, q3, q4, q5, q6;
+ CounterCell(long x) { value = x; }
+ }
/**
- * Saves the state of the <tt>ConcurrentHashMap</tt> instance to a
- * stream (i.e., serializes it).
- * @param s the stream
- * @serialData
- * the key (Object) and value (Object)
- * for each key-value mapping, followed by a null pair.
- * The key-value mappings are emitted in no particular order.
+ * Holder for the thread-local hash code determining which
+ * CounterCell to use. The code is initialized via the
+ * counterHashCodeGenerator, but may be moved upon collisions.
*/
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException {
- // force all segments for serialization compatibility
- for (int k = 0; k < segments.length; ++k)
- ensureSegment(k);
- s.defaultWriteObject();
-
- final Segment<K,V>[] segments = this.segments;
- for (int k = 0; k < segments.length; ++k) {
- Segment<K,V> seg = segmentAt(segments, k);
- seg.lock();
- try {
- HashEntry<K,V>[] tab = seg.table;
- for (int i = 0; i < tab.length; ++i) {
- HashEntry<K,V> e;
- for (e = entryAt(tab, i); e != null; e = e.next) {
- s.writeObject(e.key);
- s.writeObject(e.value);
- }
- }
- } finally {
- seg.unlock();
- }
- }
- s.writeObject(null);
- s.writeObject(null);
+ static final class CounterHashCode {
+ int code;
}
/**
- * Reconstitutes the <tt>ConcurrentHashMap</tt> instance from a
- * stream (i.e., deserializes it).
- * @param s the stream
+ * Generates initial value for per-thread CounterHashCodes.
*/
- @SuppressWarnings("unchecked")
- private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- s.defaultReadObject();
+ static final AtomicInteger counterHashCodeGenerator = new AtomicInteger();
- // Re-initialize segments to be minimally sized, and let grow.
- int cap = MIN_SEGMENT_TABLE_CAPACITY;
- final Segment<K,V>[] segments = this.segments;
- for (int k = 0; k < segments.length; ++k) {
- Segment<K,V> seg = segments[k];
- if (seg != null) {
- seg.threshold = (int)(cap * seg.loadFactor);
- seg.table = (HashEntry<K,V>[]) new HashEntry<?,?>[cap];
+ /**
+ * Increment for counterHashCodeGenerator. See class ThreadLocal
+ * for explanation.
+ */
+ static final int SEED_INCREMENT = 0x61c88647;
+
+ /**
+ * Per-thread counter hash codes. Shared across all instances.
+ */
+ static final ThreadLocal<CounterHashCode> threadCounterHashCode =
+ new ThreadLocal<CounterHashCode>();
+
+ final long sumCount() {
+ CounterCell[] as = counterCells; CounterCell a;
+ long sum = baseCount;
+ if (as != null) {
+ for (int i = 0; i < as.length; ++i) {
+ if ((a = as[i]) != null)
+ sum += a.value;
}
}
+ return sum;
+ }
- // Read the keys and values, and put the mappings in the table
+ // See LongAdder version for explanation
+ private final void fullAddCount(long x, CounterHashCode hc,
+ boolean wasUncontended) {
+ int h;
+ if (hc == null) {
+ hc = new CounterHashCode();
+ int s = counterHashCodeGenerator.addAndGet(SEED_INCREMENT);
+ h = hc.code = (s == 0) ? 1 : s; // Avoid zero
+ threadCounterHashCode.set(hc);
+ }
+ else
+ h = hc.code;
+ boolean collide = false; // True if last slot nonempty
for (;;) {
- K key = (K) s.readObject();
- V value = (V) s.readObject();
- if (key == null)
- break;
- put(key, value);
+ CounterCell[] as; CounterCell a; int n; long v;
+ if ((as = counterCells) != null && (n = as.length) > 0) {
+ if ((a = as[(n - 1) & h]) == null) {
+ if (cellsBusy == 0) { // Try to attach new Cell
+ CounterCell r = new CounterCell(x); // Optimistic create
+ if (cellsBusy == 0 &&
+ U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
+ boolean created = false;
+ try { // Recheck under lock
+ CounterCell[] rs; int m, j;
+ if ((rs = counterCells) != null &&
+ (m = rs.length) > 0 &&
+ rs[j = (m - 1) & h] == null) {
+ rs[j] = r;
+ created = true;
+ }
+ } finally {
+ cellsBusy = 0;
+ }
+ if (created)
+ break;
+ continue; // Slot is now non-empty
+ }
+ }
+ collide = false;
+ }
+ else if (!wasUncontended) // CAS already known to fail
+ wasUncontended = true; // Continue after rehash
+ else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
+ break;
+ else if (counterCells != as || n >= NCPU)
+ collide = false; // At max size or stale
+ else if (!collide)
+ collide = true;
+ else if (cellsBusy == 0 &&
+ U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
+ try {
+ if (counterCells == as) {// Expand table unless stale
+ CounterCell[] rs = new CounterCell[n << 1];
+ for (int i = 0; i < n; ++i)
+ rs[i] = as[i];
+ counterCells = rs;
+ }
+ } finally {
+ cellsBusy = 0;
+ }
+ collide = false;
+ continue; // Retry with expanded table
+ }
+ h ^= h << 13; // Rehash
+ h ^= h >>> 17;
+ h ^= h << 5;
+ }
+ else if (cellsBusy == 0 && counterCells == as &&
+ U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
+ boolean init = false;
+ try { // Initialize table
+ if (counterCells == as) {
+ CounterCell[] rs = new CounterCell[2];
+ rs[h & 1] = new CounterCell(x);
+ counterCells = rs;
+ init = true;
+ }
+ } finally {
+ cellsBusy = 0;
+ }
+ if (init)
+ break;
+ }
+ else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
+ break; // Fall back on using base
}
+ hc.code = h; // Record index for next time
}
// Unsafe mechanics
- private static final sun.misc.Unsafe UNSAFE;
- private static final long SBASE;
- private static final int SSHIFT;
- private static final long TBASE;
- private static final int TSHIFT;
+ private static final sun.misc.Unsafe U;
+ private static final long SIZECTL;
+ private static final long TRANSFERINDEX;
+ private static final long TRANSFERORIGIN;
+ private static final long BASECOUNT;
+ private static final long CELLSBUSY;
+ private static final long CELLVALUE;
+ private static final long ABASE;
+ private static final int ASHIFT;
static {
- int ss, ts;
try {
- UNSAFE = sun.misc.Unsafe.getUnsafe();
- Class<?> tc = HashEntry[].class;
- Class<?> sc = Segment[].class;
- TBASE = UNSAFE.arrayBaseOffset(tc);
- SBASE = UNSAFE.arrayBaseOffset(sc);
- ts = UNSAFE.arrayIndexScale(tc);
- ss = UNSAFE.arrayIndexScale(sc);
+ U = sun.misc.Unsafe.getUnsafe();
+ Class<?> k = ConcurrentHashMap.class;
+ SIZECTL = U.objectFieldOffset
+ (k.getDeclaredField("sizeCtl"));
+ TRANSFERINDEX = U.objectFieldOffset
+ (k.getDeclaredField("transferIndex"));
+ TRANSFERORIGIN = U.objectFieldOffset
+ (k.getDeclaredField("transferOrigin"));
+ BASECOUNT = U.objectFieldOffset
+ (k.getDeclaredField("baseCount"));
+ CELLSBUSY = U.objectFieldOffset
+ (k.getDeclaredField("cellsBusy"));
+ Class<?> ck = CounterCell.class;
+ CELLVALUE = U.objectFieldOffset
+ (ck.getDeclaredField("value"));
+ Class<?> ak = Node[].class;
+ ABASE = U.arrayBaseOffset(ak);
+ int scale = U.arrayIndexScale(ak);
+ if ((scale & (scale - 1)) != 0)
+ throw new Error("data type scale not a power of two");
+ ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
- if ((ss & (ss-1)) != 0 || (ts & (ts-1)) != 0)
- throw new Error("data type scale not a power of two");
- SSHIFT = 31 - Integer.numberOfLeadingZeros(ss);
- TSHIFT = 31 - Integer.numberOfLeadingZeros(ts);
}
}
diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java
index 54b53ae..b38d6a5 100644
--- a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java
+++ b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java
@@ -56,8 +56,6 @@ import java.util.Queue;
* actions subsequent to the access or removal of that element from
* the {@code ConcurrentLinkedDeque} in another thread.
*
- * @hide
- *
* @since 1.7
* @author Doug Lea
* @author Martin Buchholz
diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java
index 873f825..b39a533 100644
--- a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java
+++ b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java
@@ -31,7 +31,7 @@ import java.util.Queue;
* Like most other concurrent collection implementations, this class
* does not permit the use of {@code null} elements.
*
- * <p>This implementation employs an efficient &quot;wait-free&quot;
+ * <p>This implementation employs an efficient <em>non-blocking</em>
* algorithm based on one described in <a
* href="http://www.cs.rochester.edu/u/michael/PODC96.html"> Simple,
* Fast, and Practical Non-Blocking and Blocking Concurrent Queue
diff --git a/luni/src/main/java/java/util/concurrent/CountedCompleter.java b/luni/src/main/java/java/util/concurrent/CountedCompleter.java
index ffe7582..d5f794e 100644
--- a/luni/src/main/java/java/util/concurrent/CountedCompleter.java
+++ b/luni/src/main/java/java/util/concurrent/CountedCompleter.java
@@ -8,14 +8,15 @@ package java.util.concurrent;
/**
* A {@link ForkJoinTask} with a completion action performed when
- * triggered and there are no remaining pending
- * actions. CountedCompleters are in general more robust in the
+ * triggered and there are no remaining pending actions.
+ * CountedCompleters are in general more robust in the
* presence of subtask stalls and blockage than are other forms of
* ForkJoinTasks, but are less intuitive to program. Uses of
* CountedCompleter are similar to those of other completion based
* components (such as {@link java.nio.channels.CompletionHandler})
* except that multiple <em>pending</em> completions may be necessary
- * to trigger the completion action {@link #onCompletion}, not just one.
+ * to trigger the completion action {@link #onCompletion(CountedCompleter)},
+ * not just one.
* Unless initialized otherwise, the {@linkplain #getPendingCount pending
* count} starts at zero, but may be (atomically) changed using
* methods {@link #setPendingCount}, {@link #addToPendingCount}, and
@@ -40,9 +41,10 @@ package java.util.concurrent;
* <p>A concrete CountedCompleter class must define method {@link
* #compute}, that should in most cases (as illustrated below), invoke
* {@code tryComplete()} once before returning. The class may also
- * optionally override method {@link #onCompletion} to perform an
- * action upon normal completion, and method {@link
- * #onExceptionalCompletion} to perform an action upon any exception.
+ * optionally override method {@link #onCompletion(CountedCompleter)}
+ * to perform an action upon normal completion, and method
+ * {@link #onExceptionalCompletion(Throwable, CountedCompleter)} to
+ * perform an action upon any exception.
*
* <p>CountedCompleters most often do not bear results, in which case
* they are normally declared as {@code CountedCompleter<Void>}, and
@@ -63,13 +65,14 @@ package java.util.concurrent;
* only as an internal helper for other computations, so its own task
* status (as reported in methods such as {@link ForkJoinTask#isDone})
* is arbitrary; this status changes only upon explicit invocations of
- * {@link #complete}, {@link ForkJoinTask#cancel}, {@link
- * ForkJoinTask#completeExceptionally} or upon exceptional completion
- * of method {@code compute}. Upon any exceptional completion, the
- * exception may be relayed to a task's completer (and its completer,
- * and so on), if one exists and it has not otherwise already
- * completed. Similarly, cancelling an internal CountedCompleter has
- * only a local effect on that completer, so is not often useful.
+ * {@link #complete}, {@link ForkJoinTask#cancel},
+ * {@link ForkJoinTask#completeExceptionally(Throwable)} or upon
+ * exceptional completion of method {@code compute}. Upon any
+ * exceptional completion, the exception may be relayed to a task's
+ * completer (and its completer, and so on), if one exists and it has
+ * not otherwise already completed. Similarly, cancelling an internal
+ * CountedCompleter has only a local effect on that completer, so is
+ * not often useful.
*
* <p><b>Sample Usages.</b>
*
@@ -96,8 +99,8 @@ package java.util.concurrent;
* improve load balancing. In the recursive case, the second of each
* pair of subtasks to finish triggers completion of its parent
* (because no result combination is performed, the default no-op
- * implementation of method {@code onCompletion} is not overridden). A
- * static utility method sets up the base task and invokes it
+ * implementation of method {@code onCompletion} is not overridden).
+ * A static utility method sets up the base task and invokes it
* (here, implicitly using the {@link ForkJoinPool#commonPool()}).
*
* <pre> {@code
@@ -152,12 +155,11 @@ package java.util.concurrent;
* }
* }</pre>
*
- * As a further improvement, notice that the left task need not even
- * exist. Instead of creating a new one, we can iterate using the
- * original task, and add a pending count for each fork. Additionally,
- * because no task in this tree implements an {@link #onCompletion}
- * method, {@code tryComplete()} can be replaced with {@link
- * #propagateCompletion}.
+ * As a further improvement, notice that the left task need not even exist.
+ * Instead of creating a new one, we can iterate using the original task,
+ * and add a pending count for each fork. Additionally, because no task
+ * in this tree implements an {@link #onCompletion(CountedCompleter)} method,
+ * {@code tryComplete()} can be replaced with {@link #propagateCompletion}.
*
* <pre> {@code
* class ForEach<E> ...
@@ -235,7 +237,7 @@ package java.util.concurrent;
*
* <p><b>Recording subtasks.</b> CountedCompleter tasks that combine
* results of multiple subtasks usually need to access these results
- * in method {@link #onCompletion}. As illustrated in the following
+ * in method {@link #onCompletion(CountedCompleter)}. As illustrated in the following
* class (that performs a simplified form of map-reduce where mappings
* and reductions are all of type {@code E}), one way to do this in
* divide and conquer designs is to have each subtask record its
@@ -357,7 +359,7 @@ package java.util.concurrent;
*
* <p><b>Triggers.</b> Some CountedCompleters are themselves never
* forked, but instead serve as bits of plumbing in other designs;
- * including those in which the completion of one of more async tasks
+ * including those in which the completion of one or more async tasks
* triggers another async task. For example:
*
* <pre> {@code
@@ -438,20 +440,21 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
}
/**
- * Performs an action when method {@link #completeExceptionally}
- * is invoked or method {@link #compute} throws an exception, and
- * this task has not otherwise already completed normally. On
- * entry to this method, this task {@link
- * ForkJoinTask#isCompletedAbnormally}. The return value of this
- * method controls further propagation: If {@code true} and this
- * task has a completer, then this completer is also completed
- * exceptionally. The default implementation of this method does
- * nothing except return {@code true}.
+ * Performs an action when method {@link
+ * #completeExceptionally(Throwable)} is invoked or method {@link
+ * #compute} throws an exception, and this task has not already
+ * otherwise completed normally. On entry to this method, this task
+ * {@link ForkJoinTask#isCompletedAbnormally}. The return value
+ * of this method controls further propagation: If {@code true}
+ * and this task has a completer that has not completed, then that
+ * completer is also completed exceptionally, with the same
+ * exception as this completer. The default implementation of
+ * this method does nothing except return {@code true}.
*
* @param ex the exception
* @param caller the task invoking this method (which may
* be this task itself)
- * @return true if this exception should be propagated to this
+ * @return {@code true} if this exception should be propagated to this
* task's completer, if one exists
*/
public boolean onExceptionalCompletion(Throwable ex, CountedCompleter<?> caller) {
@@ -492,7 +495,7 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
* @param delta the value to add
*/
public final void addToPendingCount(int delta) {
- int c; // note: can replace with intrinsic in jdk8
+ int c;
do {} while (!U.compareAndSwapInt(this, PENDING, c = pending, c+delta));
}
@@ -502,7 +505,7 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
*
* @param expected the expected value
* @param count the new value
- * @return true if successful
+ * @return {@code true} if successful
*/
public final boolean compareAndSetPendingCount(int expected, int count) {
return U.compareAndSwapInt(this, PENDING, expected, count);
@@ -536,9 +539,9 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
/**
* If the pending count is nonzero, decrements the count;
- * otherwise invokes {@link #onCompletion} and then similarly
- * tries to complete this task's completer, if one exists,
- * else marks this task as complete.
+ * otherwise invokes {@link #onCompletion(CountedCompleter)}
+ * and then similarly tries to complete this task's completer,
+ * if one exists, else marks this task as complete.
*/
public final void tryComplete() {
CountedCompleter<?> a = this, s = a;
@@ -557,12 +560,12 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
/**
* Equivalent to {@link #tryComplete} but does not invoke {@link
- * #onCompletion} along the completion path: If the pending count
- * is nonzero, decrements the count; otherwise, similarly tries to
- * complete this task's completer, if one exists, else marks this
- * task as complete. This method may be useful in cases where
- * {@code onCompletion} should not, or need not, be invoked for
- * each completer in a computation.
+ * #onCompletion(CountedCompleter)} along the completion path:
+ * If the pending count is nonzero, decrements the count;
+ * otherwise, similarly tries to complete this task's completer, if
+ * one exists, else marks this task as complete. This method may be
+ * useful in cases where {@code onCompletion} should not, or need
+ * not, be invoked for each completer in a computation.
*/
public final void propagateCompletion() {
CountedCompleter<?> a = this, s = a;
@@ -579,13 +582,15 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
}
/**
- * Regardless of pending count, invokes {@link #onCompletion},
- * marks this task as complete and further triggers {@link
- * #tryComplete} on this task's completer, if one exists. The
- * given rawResult is used as an argument to {@link #setRawResult}
- * before invoking {@link #onCompletion} or marking this task as
- * complete; its value is meaningful only for classes overriding
- * {@code setRawResult}.
+ * Regardless of pending count, invokes
+ * {@link #onCompletion(CountedCompleter)}, marks this task as
+ * complete and further triggers {@link #tryComplete} on this
+ * task's completer, if one exists. The given rawResult is
+ * used as an argument to {@link #setRawResult} before invoking
+ * {@link #onCompletion(CountedCompleter)} or marking this task
+ * as complete; its value is meaningful only for classes
+ * overriding {@code setRawResult}. This method does not modify
+ * the pending count.
*
* <p>This method may be useful when forcing completion as soon as
* any one (versus all) of several subtask results are obtained.
@@ -604,7 +609,6 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
p.tryComplete();
}
-
/**
* If this task's pending count is zero, returns this task;
* otherwise decrements its pending count and returns {@code
@@ -668,8 +672,9 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
void internalPropagateException(Throwable ex) {
CountedCompleter<?> a = this, s = a;
while (a.onExceptionalCompletion(ex, s) &&
- (a = (s = a).completer) != null && a.status >= 0)
- a.recordExceptionalCompletion(ex);
+ (a = (s = a).completer) != null && a.status >= 0 &&
+ a.recordExceptionalCompletion(ex) == EXCEPTIONAL)
+ ;
}
/**
diff --git a/luni/src/main/java/java/util/concurrent/ForkJoinPool.java b/luni/src/main/java/java/util/concurrent/ForkJoinPool.java
index 87ffff3..9448616 100644
--- a/luni/src/main/java/java/util/concurrent/ForkJoinPool.java
+++ b/luni/src/main/java/java/util/concurrent/ForkJoinPool.java
@@ -6,6 +6,7 @@
package java.util.concurrent;
+import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -17,6 +18,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RunnableFuture;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
/**
@@ -37,7 +39,7 @@ import java.util.concurrent.TimeUnit;
* ForkJoinPool}s may also be appropriate for use with event-style
* tasks that are never joined.
*
- * <p>A static {@link #commonPool()} is available and appropriate for
+ * <p>A static {@code commonPool()} is available and appropriate for
* most applications. The common pool is used by any ForkJoinTask that
* is not explicitly submitted to a specified pool. Using the common
* pool normally reduces resource usage (its threads are slowly
@@ -49,9 +51,9 @@ import java.util.concurrent.TimeUnit;
* level; by default, equal to the number of available processors. The
* pool attempts to maintain enough active (or available) threads by
* dynamically adding, suspending, or resuming internal worker
- * threads, even if some tasks are stalled waiting to join
- * others. However, no such adjustments are guaranteed in the face of
- * blocked I/O or other unmanaged synchronization. The nested {@link
+ * threads, even if some tasks are stalled waiting to join others.
+ * However, no such adjustments are guaranteed in the face of blocked
+ * I/O or other unmanaged synchronization. The nested {@link
* ManagedBlocker} interface enables extension of the kinds of
* synchronization accommodated.
*
@@ -75,38 +77,45 @@ import java.util.concurrent.TimeUnit;
* there is little difference among choice of methods.
*
* <table BORDER CELLPADDING=3 CELLSPACING=1>
+ * <caption>Summary of task execution methods</caption>
* <tr>
* <td></td>
* <td ALIGN=CENTER> <b>Call from non-fork/join clients</b></td>
* <td ALIGN=CENTER> <b>Call from within fork/join computations</b></td>
* </tr>
* <tr>
- * <td> <b>Arrange async execution</td>
+ * <td> <b>Arrange async execution</b></td>
* <td> {@link #execute(ForkJoinTask)}</td>
* <td> {@link ForkJoinTask#fork}</td>
* </tr>
* <tr>
- * <td> <b>Await and obtain result</td>
+ * <td> <b>Await and obtain result</b></td>
* <td> {@link #invoke(ForkJoinTask)}</td>
* <td> {@link ForkJoinTask#invoke}</td>
* </tr>
* <tr>
- * <td> <b>Arrange exec and obtain Future</td>
+ * <td> <b>Arrange exec and obtain Future</b></td>
* <td> {@link #submit(ForkJoinTask)}</td>
* <td> {@link ForkJoinTask#fork} (ForkJoinTasks <em>are</em> Futures)</td>
* </tr>
* </table>
*
* <p>The common pool is by default constructed with default
- * parameters, but these may be controlled by setting three {@link
- * System#getProperty system properties} with prefix {@code
- * java.util.concurrent.ForkJoinPool.common}: {@code parallelism} --
- * an integer greater than zero, {@code threadFactory} -- the class
- * name of a {@link ForkJoinWorkerThreadFactory}, and {@code
- * exceptionHandler} -- the class name of a {@link
- * java.lang.Thread.UncaughtExceptionHandler
- * Thread.UncaughtExceptionHandler}. Upon any error in establishing
- * these settings, default parameters are used.
+ * parameters, but these may be controlled by setting three
+ * {@linkplain System#getProperty system properties}:
+ * <ul>
+ * <li>{@code java.util.concurrent.ForkJoinPool.common.parallelism}
+ * - the parallelism level, a non-negative integer
+ * <li>{@code java.util.concurrent.ForkJoinPool.common.threadFactory}
+ * - the class name of a {@link ForkJoinWorkerThreadFactory}
+ * <li>{@code java.util.concurrent.ForkJoinPool.common.exceptionHandler}
+ * - the class name of a {@link UncaughtExceptionHandler}
+ * </ul>
+ * The system class loader is used to load these classes.
+ * Upon any error in establishing these settings, default parameters
+ * are used. It is possible to disable or limit the use of threads in
+ * the common pool by setting the parallelism property to zero, and/or
+ * using a factory that may return {@code null}.
*
* <p><b>Implementation notes</b>: This implementation restricts the
* maximum number of running threads to 32767. Attempts to create
@@ -118,7 +127,6 @@ import java.util.concurrent.TimeUnit;
* or internal resources have been exhausted.
*
* @since 1.7
- * @hide
* @author Doug Lea
*/
public class ForkJoinPool extends AbstractExecutorService {
@@ -153,32 +161,35 @@ public class ForkJoinPool extends AbstractExecutorService {
* (http://research.sun.com/scalable/pubs/index.html) and
* "Idempotent work stealing" by Michael, Saraswat, and Vechev,
* PPoPP 2009 (http://portal.acm.org/citation.cfm?id=1504186).
- * The main differences ultimately stem from GC requirements that
- * we null out taken slots as soon as we can, to maintain as small
- * a footprint as possible even in programs generating huge
- * numbers of tasks. To accomplish this, we shift the CAS
- * arbitrating pop vs poll (steal) from being on the indices
- * ("base" and "top") to the slots themselves. So, both a
- * successful pop and poll mainly entail a CAS of a slot from
- * non-null to null. Because we rely on CASes of references, we
- * do not need tag bits on base or top. They are simple ints as
- * used in any circular array-based queue (see for example
- * ArrayDeque). Updates to the indices must still be ordered in a
- * way that guarantees that top == base means the queue is empty,
- * but otherwise may err on the side of possibly making the queue
- * appear nonempty when a push, pop, or poll have not fully
- * committed. Note that this means that the poll operation,
- * considered individually, is not wait-free. One thief cannot
- * successfully continue until another in-progress one (or, if
- * previously empty, a push) completes. However, in the
- * aggregate, we ensure at least probabilistic non-blockingness.
- * If an attempted steal fails, a thief always chooses a different
- * random victim target to try next. So, in order for one thief to
- * progress, it suffices for any in-progress poll or new push on
- * any empty queue to complete. (This is why we normally use
- * method pollAt and its variants that try once at the apparent
- * base index, else consider alternative actions, rather than
- * method poll.)
+ * See also "Correct and Efficient Work-Stealing for Weak Memory
+ * Models" by Le, Pop, Cohen, and Nardelli, PPoPP 2013
+ * (http://www.di.ens.fr/~zappa/readings/ppopp13.pdf) for an
+ * analysis of memory ordering (atomic, volatile etc) issues. The
+ * main differences ultimately stem from GC requirements that we
+ * null out taken slots as soon as we can, to maintain as small a
+ * footprint as possible even in programs generating huge numbers
+ * of tasks. To accomplish this, we shift the CAS arbitrating pop
+ * vs poll (steal) from being on the indices ("base" and "top") to
+ * the slots themselves. So, both a successful pop and poll
+ * mainly entail a CAS of a slot from non-null to null. Because
+ * we rely on CASes of references, we do not need tag bits on base
+ * or top. They are simple ints as used in any circular
+ * array-based queue (see for example ArrayDeque). Updates to the
+ * indices must still be ordered in a way that guarantees that top
+ * == base means the queue is empty, but otherwise may err on the
+ * side of possibly making the queue appear nonempty when a push,
+ * pop, or poll have not fully committed. Note that this means
+ * that the poll operation, considered individually, is not
+ * wait-free. One thief cannot successfully continue until another
+ * in-progress one (or, if previously empty, a push) completes.
+ * However, in the aggregate, we ensure at least probabilistic
+ * non-blockingness. If an attempted steal fails, a thief always
+ * chooses a different random victim target to try next. So, in
+ * order for one thief to progress, it suffices for any
+ * in-progress poll or new push on any empty queue to
+ * complete. (This is why we normally use method pollAt and its
+ * variants that try once at the apparent base index, else
+ * consider alternative actions, rather than method poll.)
*
* This approach also enables support of a user mode in which local
* task processing is in FIFO, not LIFO order, simply by using
@@ -197,18 +208,17 @@ public class ForkJoinPool extends AbstractExecutorService {
* for work-stealing (this would contaminate lifo/fifo
* processing). Instead, we randomly associate submission queues
* with submitting threads, using a form of hashing. The
- * ThreadLocal Submitter class contains a value initially used as
- * a hash code for choosing existing queues, but may be randomly
- * repositioned upon contention with other submitters. In
- * essence, submitters act like workers except that they are
- * restricted to executing local tasks that they submitted (or in
- * the case of CountedCompleters, others with the same root task).
- * However, because most shared/external queue operations are more
- * expensive than internal, and because, at steady state, external
- * submitters will compete for CPU with workers, ForkJoinTask.join
- * and related methods disable them from repeatedly helping to
- * process tasks if all workers are active. Insertion of tasks in
- * shared mode requires a lock (mainly to protect in the case of
+ * Submitter probe value serves as a hash code for
+ * choosing existing queues, and may be randomly repositioned upon
+ * contention with other submitters. In essence, submitters act
+ * like workers except that they are restricted to executing local
+ * tasks that they submitted. However, because most
+ * shared/external queue operations are more expensive than
+ * internal, and because, at steady state, external submitters
+ * will compete for CPU with workers, ForkJoinTask.join and
+ * related methods disable them from repeatedly helping to process
+ * tasks if all workers are active. Insertion of tasks in shared
+ * mode requires a lock (mainly to protect in the case of
* resizing) but we use only a simple spinlock (using bits in
* field qlock), because submitters encountering a busy queue move
* on to try or create other queues -- they block only when
@@ -298,37 +308,35 @@ public class ForkJoinPool extends AbstractExecutorService {
* has not yet entered the wait queue. We solve this by requiring
* a full sweep of all workers (via repeated calls to method
* scan()) both before and after a newly waiting worker is added
- * to the wait queue. During a rescan, the worker might release
- * some other queued worker rather than itself, which has the same
- * net effect. Because enqueued workers may actually be rescanning
- * rather than waiting, we set and clear the "parker" field of
- * WorkQueues to reduce unnecessary calls to unpark. (This
- * requires a secondary recheck to avoid missed signals.) Note
- * the unusual conventions about Thread.interrupts surrounding
- * parking and other blocking: Because interrupts are used solely
- * to alert threads to check termination, which is checked anyway
- * upon blocking, we clear status (using Thread.interrupted)
- * before any call to park, so that park does not immediately
- * return due to status being set via some other unrelated call to
- * interrupt in user code.
+ * to the wait queue. Because enqueued workers may actually be
+ * rescanning rather than waiting, we set and clear the "parker"
+ * field of WorkQueues to reduce unnecessary calls to unpark.
+ * (This requires a secondary recheck to avoid missed signals.)
+ * Note the unusual conventions about Thread.interrupts
+ * surrounding parking and other blocking: Because interrupts are
+ * used solely to alert threads to check termination, which is
+ * checked anyway upon blocking, we clear status (using
+ * Thread.interrupted) before any call to park, so that park does
+ * not immediately return due to status being set via some other
+ * unrelated call to interrupt in user code.
*
* Signalling. We create or wake up workers only when there
* appears to be at least one task they might be able to find and
- * execute. However, many other threads may notice the same task
- * and each signal to wake up a thread that might take it. So in
- * general, pools will be over-signalled. When a submission is
- * added or another worker adds a task to a queue that has fewer
- * than two tasks, they signal waiting workers (or trigger
- * creation of new ones if fewer than the given parallelism level
- * -- signalWork), and may leave a hint to the unparked worker to
- * help signal others upon wakeup). These primary signals are
- * buttressed by others (see method helpSignal) whenever other
- * threads scan for work or do not have a task to process. On
- * most platforms, signalling (unpark) overhead time is noticeably
+ * execute. When a submission is added or another worker adds a
+ * task to a queue that has fewer than two tasks, they signal
+ * waiting workers (or trigger creation of new ones if fewer than
+ * the given parallelism level -- signalWork). These primary
+ * signals are buttressed by others whenever other threads remove
+ * a task from a queue and notice that there are other tasks there
+ * as well. So in general, pools will be over-signalled. On most
+ * platforms, signalling (unpark) overhead time is noticeably
* long, and the time between signalling a thread and it actually
* making progress can be very noticeably long, so it is worth
* offloading these delays from critical paths as much as
- * possible.
+ * possible. Additionally, workers spin-down gradually, by staying
+ * alive so long as they see the ctl state changing. Similar
+ * stability-sensing techniques are also used before blocking in
+ * awaitJoin and helpComplete.
*
* Trimming workers. To release resources after periods of lack of
* use, a worker starting to wait when the pool is quiescent will
@@ -409,12 +417,6 @@ public class ForkJoinPool extends AbstractExecutorService {
* to find work (see MAX_HELP) and fall back to suspending the
* worker and if necessary replacing it with another.
*
- * Helping actions for CountedCompleters are much simpler: Method
- * helpComplete can take and execute any task with the same root
- * as the task being waited on. However, this still entails some
- * traversal of completer chains, so is less efficient than using
- * CountedCompleters without explicit joins.
- *
* It is impossible to keep exactly the target parallelism number
* of threads running at any given time. Determining the
* existence of conservatively safe helping targets, the
@@ -441,7 +443,7 @@ public class ForkJoinPool extends AbstractExecutorService {
* Common Pool
* ===========
*
- * The static commonPool always exists after static
+ * The static common pool always exists after static
* initialization. Since it (or any other created pool) need
* never be used, we minimize initial construction overhead and
* footprint to the setup of about a dozen fields, with no nested
@@ -449,8 +451,11 @@ public class ForkJoinPool extends AbstractExecutorService {
* fullExternalPush during the first submission to the pool.
*
* When external threads submit to the common pool, they can
- * perform some subtask processing (see externalHelpJoin and
- * related methods). We do not need to record whether these
+ * perform subtask processing (see externalHelpJoin and related
+ * methods). This caller-helps policy makes it sensible to set
+ * common pool parallelism level to one (or more) less than the
+ * total number of available cores, or even zero for pure
+ * caller-runs. We do not need to record whether external
* submissions are to the common pool -- if not, externalHelpJoin
* returns quickly (at the most helping to signal some common pool
* workers). These submitters would otherwise be blocked waiting
@@ -520,6 +525,7 @@ public class ForkJoinPool extends AbstractExecutorService {
*
* @param pool the pool this thread works in
* @throws NullPointerException if the pool is null
+ * @return the new worker thread
*/
public ForkJoinWorkerThread newThread(ForkJoinPool pool);
}
@@ -536,26 +542,6 @@ public class ForkJoinPool extends AbstractExecutorService {
}
/**
- * Per-thread records for threads that submit to pools. Currently
- * holds only pseudo-random seed / index that is used to choose
- * submission queues in method externalPush. In the future, this may
- * also incorporate a means to implement different task rejection
- * and resubmission policies.
- *
- * Seeds for submitters and workers/workQueues work in basically
- * the same way but are initialized and updated using slightly
- * different mechanics. Both are initialized using the same
- * approach as in class ThreadLocal, where successive values are
- * unlikely to collide with previous values. Seeds are then
- * randomly modified upon collisions using xorshifts, which
- * requires a non-zero seed.
- */
- static final class Submitter {
- int seed;
- Submitter(int s) { seed = s; }
- }
-
- /**
* Class for artificial tasks that are used to replace the target
* of local joins if they are removed from an interior queue slot
* in WorkQueue.tryRemoveAndExec. We don't need the proxy to
@@ -614,17 +600,8 @@ public class ForkJoinPool extends AbstractExecutorService {
* do not want multiple WorkQueue instances or multiple queue
* arrays sharing cache lines. (It would be best for queue objects
* and their arrays to share, but there is nothing available to
- * help arrange that). Unfortunately, because they are recorded
- * in a common array, WorkQueue instances are often moved to be
- * adjacent by garbage collectors. To reduce impact, we use field
- * padding that works OK on common platforms; this effectively
- * trades off slightly slower average field access for the sake of
- * avoiding really bad worst-case access. (Until better JVM
- * support is in place, this padding is dependent on transient
- * properties of JVM field layout rules.) We also take care in
- * allocating, sizing and resizing the array. Non-shared queue
- * arrays are initialized by workers before use. Others are
- * allocated on first use.
+ * help arrange that). The @Contended annotation alerts JVMs to
+ * try to keep instances apart.
*/
static final class WorkQueue {
/**
@@ -650,13 +627,12 @@ public class ForkJoinPool extends AbstractExecutorService {
// Heuristic padding to ameliorate unfortunate memory placements
volatile long pad00, pad01, pad02, pad03, pad04, pad05, pad06;
- int seed; // for random scanning; initialize nonzero
volatile int eventCount; // encoded inactivation count; < 0 if inactive
int nextWait; // encoded record of next event waiter
- int hint; // steal or signal hint (index)
- int poolIndex; // index of this queue in pool (or 0)
- final int mode; // 0: lifo, > 0: fifo, < 0: shared
int nsteals; // number of steals
+ int hint; // steal index hint
+ short poolIndex; // index of this queue in pool
+ final short mode; // 0: lifo, > 0: fifo, < 0: shared
volatile int qlock; // 1: locked, -1: terminate; else 0
volatile int base; // index of next slot for poll
int top; // index of next slot for push
@@ -674,8 +650,8 @@ public class ForkJoinPool extends AbstractExecutorService {
int seed) {
this.pool = pool;
this.owner = owner;
- this.mode = mode;
- this.seed = seed;
+ this.mode = (short)mode;
+ this.hint = seed; // store initial seed for runWorker
// Place indices in the center of array (that is not yet allocated)
base = top = INITIAL_QUEUE_CAPACITY >>> 1;
}
@@ -688,7 +664,7 @@ public class ForkJoinPool extends AbstractExecutorService {
return (n >= 0) ? 0 : -n; // ignore transient negative
}
- /**
+ /**
* Provides a more accurate estimate of whether this queue has
* any tasks than does queueSize, by checking whether a
* near-empty queue has at least one unclaimed task.
@@ -713,20 +689,18 @@ public class ForkJoinPool extends AbstractExecutorService {
*/
final void push(ForkJoinTask<?> task) {
ForkJoinTask<?>[] a; ForkJoinPool p;
- int s = top, m, n;
+ int s = top, n;
if ((a = array) != null) { // ignore if queue removed
- int j = (((m = a.length - 1) & s) << ASHIFT) + ABASE;
- U.putOrderedObject(a, j, task);
- if ((n = (top = s + 1) - base) <= 2) {
- if ((p = pool) != null)
- p.signalWork(this);
- }
+ int m = a.length - 1;
+ U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
+ if ((n = (top = s + 1) - base) <= 2)
+ (p = pool).signalWork(p.workQueues, this);
else if (n >= m)
growArray();
}
}
- /**
+ /**
* Initializes or doubles the capacity of array. Call either
* by owner or with lock held -- it is OK for base, but not
* top, to move while resizings are in progress.
@@ -784,9 +758,8 @@ public class ForkJoinPool extends AbstractExecutorService {
if ((a = array) != null) {
int j = (((a.length - 1) & b) << ASHIFT) + ABASE;
if ((t = (ForkJoinTask<?>)U.getObjectVolatile(a, j)) != null &&
- base == b &&
- U.compareAndSwapObject(a, j, t, null)) {
- base = b + 1;
+ base == b && U.compareAndSwapObject(a, j, t, null)) {
+ U.putOrderedInt(this, QBASE, b + 1);
return t;
}
}
@@ -802,9 +775,8 @@ public class ForkJoinPool extends AbstractExecutorService {
int j = (((a.length - 1) & b) << ASHIFT) + ABASE;
t = (ForkJoinTask<?>)U.getObjectVolatile(a, j);
if (t != null) {
- if (base == b &&
- U.compareAndSwapObject(a, j, t, null)) {
- base = b + 1;
+ if (U.compareAndSwapObject(a, j, t, null)) {
+ U.putOrderedInt(this, QBASE, b + 1);
return t;
}
}
@@ -861,46 +833,43 @@ public class ForkJoinPool extends AbstractExecutorService {
ForkJoinTask.cancelIgnoringExceptions(t);
}
- /**
- * Computes next value for random probes. Scans don't require
- * a very high quality generator, but also not a crummy one.
- * Marsaglia xor-shift is cheap and works well enough. Note:
- * This is manually inlined in its usages in ForkJoinPool to
- * avoid writes inside busy scan loops.
- */
- final int nextSeed() {
- int r = seed;
- r ^= r << 13;
- r ^= r >>> 17;
- return seed = r ^= r << 5;
- }
-
// Specialized execution methods
/**
- * Pops and runs tasks until empty.
+ * Polls and runs tasks until empty.
*/
- private void popAndExecAll() {
- // A bit faster than repeated pop calls
- ForkJoinTask<?>[] a; int m, s; long j; ForkJoinTask<?> t;
- while ((a = array) != null && (m = a.length - 1) >= 0 &&
- (s = top - 1) - base >= 0 &&
- (t = ((ForkJoinTask<?>)
- U.getObject(a, j = ((m & s) << ASHIFT) + ABASE)))
- != null) {
- if (U.compareAndSwapObject(a, j, t, null)) {
- top = s;
- t.doExec();
- }
- }
+ final void pollAndExecAll() {
+ for (ForkJoinTask<?> t; (t = poll()) != null;)
+ t.doExec();
}
/**
- * Polls and runs tasks until empty.
+ * Executes a top-level task and any local tasks remaining
+ * after execution.
*/
- private void pollAndExecAll() {
- for (ForkJoinTask<?> t; (t = poll()) != null;)
- t.doExec();
+ final void runTask(ForkJoinTask<?> task) {
+ if ((currentSteal = task) != null) {
+ task.doExec();
+ ForkJoinTask<?>[] a = array;
+ int md = mode;
+ ++nsteals;
+ currentSteal = null;
+ if (md != 0)
+ pollAndExecAll();
+ else if (a != null) {
+ int s, m = a.length - 1;
+ while ((s = top - 1) - base >= 0) {
+ long i = ((m & s) << ASHIFT) + ABASE;
+ ForkJoinTask<?> t = (ForkJoinTask<?>)U.getObject(a, i);
+ if (t == null)
+ break;
+ if (U.compareAndSwapObject(a, i, t, null)) {
+ top = s;
+ t.doExec();
+ }
+ }
+ }
+ }
}
/**
@@ -911,13 +880,15 @@ public class ForkJoinPool extends AbstractExecutorService {
* @return false if no progress can be made, else true
*/
final boolean tryRemoveAndExec(ForkJoinTask<?> task) {
- boolean stat = true, removed = false, empty = true;
+ boolean stat;
ForkJoinTask<?>[] a; int m, s, b, n;
- if ((a = array) != null && (m = a.length - 1) >= 0 &&
+ if (task != null && (a = array) != null && (m = a.length - 1) >= 0 &&
(n = (s = top) - (b = base)) > 0) {
+ boolean removed = false, empty = true;
+ stat = true;
for (ForkJoinTask<?> t;;) { // traverse from s to b
- int j = ((--s & m) << ASHIFT) + ABASE;
- t = (ForkJoinTask<?>)U.getObjectVolatile(a, j);
+ long j = ((--s & m) << ASHIFT) + ABASE;
+ t = (ForkJoinTask<?>)U.getObject(a, j);
if (t == null) // inconsistent length
break;
else if (t == task) {
@@ -945,68 +916,95 @@ public class ForkJoinPool extends AbstractExecutorService {
break;
}
}
+ if (removed)
+ task.doExec();
}
- if (removed)
- task.doExec();
+ else
+ stat = false;
return stat;
}
/**
- * Polls for and executes the given task or any other task in
- * its CountedCompleter computation.
+ * Tries to poll for and execute the given task or any other
+ * task in its CountedCompleter computation.
*/
- final boolean pollAndExecCC(ForkJoinTask<?> root) {
- ForkJoinTask<?>[] a; int b; Object o;
- outer: while ((b = base) - top < 0 && (a = array) != null) {
+ final boolean pollAndExecCC(CountedCompleter<?> root) {
+ ForkJoinTask<?>[] a; int b; Object o; CountedCompleter<?> t, r;
+ if ((b = base) - top < 0 && (a = array) != null) {
long j = (((a.length - 1) & b) << ASHIFT) + ABASE;
- if ((o = U.getObject(a, j)) == null ||
- !(o instanceof CountedCompleter))
- break;
- for (CountedCompleter<?> t = (CountedCompleter<?>)o, r = t;;) {
- if (r == root) {
- if (base == b &&
- U.compareAndSwapObject(a, j, t, null)) {
- base = b + 1;
- t.doExec();
+ if ((o = U.getObjectVolatile(a, j)) == null)
+ return true; // retry
+ if (o instanceof CountedCompleter) {
+ for (t = (CountedCompleter<?>)o, r = t;;) {
+ if (r == root) {
+ if (base == b &&
+ U.compareAndSwapObject(a, j, t, null)) {
+ U.putOrderedInt(this, QBASE, b + 1);
+ t.doExec();
+ }
return true;
}
- else
- break; // restart
+ else if ((r = r.completer) == null)
+ break; // not part of root computation
}
- if ((r = r.completer) == null)
- break outer; // not part of root computation
}
}
return false;
}
/**
- * Executes a top-level task and any local tasks remaining
- * after execution.
+ * Tries to pop and execute the given task or any other task
+ * in its CountedCompleter computation.
*/
- final void runTask(ForkJoinTask<?> t) {
- if (t != null) {
- (currentSteal = t).doExec();
- currentSteal = null;
- ++nsteals;
- if (base - top < 0) { // process remaining local tasks
- if (mode == 0)
- popAndExecAll();
- else
- pollAndExecAll();
+ final boolean externalPopAndExecCC(CountedCompleter<?> root) {
+ ForkJoinTask<?>[] a; int s; Object o; CountedCompleter<?> t, r;
+ if (base - (s = top) < 0 && (a = array) != null) {
+ long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE;
+ if ((o = U.getObject(a, j)) instanceof CountedCompleter) {
+ for (t = (CountedCompleter<?>)o, r = t;;) {
+ if (r == root) {
+ if (U.compareAndSwapInt(this, QLOCK, 0, 1)) {
+ if (top == s && array == a &&
+ U.compareAndSwapObject(a, j, t, null)) {
+ top = s - 1;
+ qlock = 0;
+ t.doExec();
+ }
+ else
+ qlock = 0;
+ }
+ return true;
+ }
+ else if ((r = r.completer) == null)
+ break;
+ }
}
}
+ return false;
}
/**
- * Executes a non-top-level (stolen) task.
+ * Internal version
*/
- final void runSubtask(ForkJoinTask<?> t) {
- if (t != null) {
- ForkJoinTask<?> ps = currentSteal;
- (currentSteal = t).doExec();
- currentSteal = ps;
+ final boolean internalPopAndExecCC(CountedCompleter<?> root) {
+ ForkJoinTask<?>[] a; int s; Object o; CountedCompleter<?> t, r;
+ if (base - (s = top) < 0 && (a = array) != null) {
+ long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE;
+ if ((o = U.getObject(a, j)) instanceof CountedCompleter) {
+ for (t = (CountedCompleter<?>)o, r = t;;) {
+ if (r == root) {
+ if (U.compareAndSwapObject(a, j, t, null)) {
+ top = s - 1;
+ t.doExec();
+ }
+ return true;
+ }
+ else if ((r = r.completer) == null)
+ break;
+ }
+ }
}
+ return false;
}
/**
@@ -1023,6 +1021,7 @@ public class ForkJoinPool extends AbstractExecutorService {
// Unsafe mechanics
private static final sun.misc.Unsafe U;
+ private static final long QBASE;
private static final long QLOCK;
private static final int ABASE;
private static final int ASHIFT;
@@ -1031,6 +1030,8 @@ public class ForkJoinPool extends AbstractExecutorService {
U = sun.misc.Unsafe.getUnsafe();
Class<?> k = WorkQueue.class;
Class<?> ak = ForkJoinTask[].class;
+ QBASE = U.objectFieldOffset
+ (k.getDeclaredField("base"));
QLOCK = U.objectFieldOffset
(k.getDeclaredField("qlock"));
ABASE = U.arrayBaseOffset(ak);
@@ -1047,13 +1048,6 @@ public class ForkJoinPool extends AbstractExecutorService {
// static fields (initialized in static initializer below)
/**
- * Creates a new ForkJoinWorkerThread. This factory is used unless
- * overridden in ForkJoinPool constructors.
- */
- public static final ForkJoinWorkerThreadFactory
- defaultForkJoinWorkerThreadFactory;
-
- /**
* Per-thread submission bookkeeping. Shared across all pools
* to reduce ThreadLocal pollution and because random motion
* to avoid contention in one pool is likely to hold for others.
@@ -1063,6 +1057,13 @@ public class ForkJoinPool extends AbstractExecutorService {
static final ThreadLocal<Submitter> submitters;
/**
+ * Creates a new ForkJoinWorkerThread. This factory is used unless
+ * overridden in ForkJoinPool constructors.
+ */
+ public static final ForkJoinWorkerThreadFactory
+ defaultForkJoinWorkerThreadFactory;
+
+ /**
* Permission required for callers of methods that may start or
* kill threads.
*/
@@ -1074,12 +1075,15 @@ public class ForkJoinPool extends AbstractExecutorService {
* to paranoically avoid potential initialization circularities
* as well as to simplify generated code.
*/
- static final ForkJoinPool commonPool;
+ static final ForkJoinPool common;
/**
- * Common pool parallelism. Must equal commonPool.parallelism.
+ * Common pool parallelism. To allow simpler use and management
+ * when common pool threads are disabled, we allow the underlying
+ * common.parallelism field to be zero, but in that case still report
+ * parallelism as 1 to reflect resulting caller-runs mechanics.
*/
- static final int commonPoolParallelism;
+ static final int commonParallelism;
/**
* Sequence number for creating workerNamePrefix.
@@ -1114,7 +1118,7 @@ public class ForkJoinPool extends AbstractExecutorService {
/**
* Tolerance for idle timeouts, to cope with timer undershoots
*/
- private static final long TIMEOUT_SLOP = 2000000L; // 20ms
+ private static final long TIMEOUT_SLOP = 2000000L;
/**
* The maximum stolen->joining link depth allowed in method
@@ -1216,30 +1220,19 @@ public class ForkJoinPool extends AbstractExecutorService {
static final int FIFO_QUEUE = 1;
static final int SHARED_QUEUE = -1;
- // bounds for #steps in scan loop -- must be power 2 minus 1
- private static final int MIN_SCAN = 0x1ff; // cover estimation slop
- private static final int MAX_SCAN = 0x1ffff; // 4 * max workers
-
- // Instance fields
-
- /*
- * Field layout of this class tends to matter more than one would
- * like. Runtime layout order is only loosely related to
- * declaration order and may differ across JVMs, but the following
- * empirically works OK on current JVMs.
- */
-
// Heuristic padding to ameliorate unfortunate memory placements
volatile long pad00, pad01, pad02, pad03, pad04, pad05, pad06;
+ // Instance fields
volatile long stealCount; // collects worker counts
volatile long ctl; // main pool control
volatile int plock; // shutdown status and seqLock
volatile int indexSeed; // worker/submitter index seed
- final int config; // mode and parallelism level
+ final short parallelism; // parallelism level
+ final short mode; // LIFO/FIFO
WorkQueue[] workQueues; // main registry
final ForkJoinWorkerThreadFactory factory;
- final Thread.UncaughtExceptionHandler ueh; // per-worker UEH
+ final UncaughtExceptionHandler ueh; // per-worker UEH
final String workerNamePrefix; // to create worker name string
volatile Object pad10, pad11, pad12, pad13, pad14, pad15, pad16, pad17;
@@ -1254,24 +1247,13 @@ public class ForkJoinPool extends AbstractExecutorService {
* a more conservative alternative to a pure spinlock.
*/
private int acquirePlock() {
- int spins = PL_SPINS, r = 0, ps, nps;
+ int spins = PL_SPINS, ps, nps;
for (;;) {
if (((ps = plock) & PL_LOCK) == 0 &&
U.compareAndSwapInt(this, PLOCK, ps, nps = ps + PL_LOCK))
return nps;
- else if (r == 0) { // randomize spins if possible
- Thread t = Thread.currentThread(); WorkQueue w; Submitter z;
- if ((t instanceof ForkJoinWorkerThread) &&
- (w = ((ForkJoinWorkerThread)t).workQueue) != null)
- r = w.seed;
- else if ((z = submitters.get()) != null)
- r = z.seed;
- else
- r = 1;
- }
else if (spins >= 0) {
- r ^= r << 1; r ^= r >>> 3; r ^= r << 10; // xorshift
- if (r >= 0)
+ if (ThreadLocalRandom.current().nextInt() >= 0)
--spins;
}
else if (U.compareAndSwapInt(this, PLOCK, ps, ps | PL_SIGNAL)) {
@@ -1303,48 +1285,15 @@ public class ForkJoinPool extends AbstractExecutorService {
}
/**
- * Performs secondary initialization, called when plock is zero.
- * Creates workQueue array and sets plock to a valid value. The
- * lock body must be exception-free (so no try/finally) so we
- * optimistically allocate new array outside the lock and throw
- * away if (very rarely) not needed. (A similar tactic is used in
- * fullExternalPush.) Because the plock seq value can eventually
- * wrap around zero, this method harmlessly fails to reinitialize
- * if workQueues exists, while still advancing plock.
- *
- * Additionally tries to create the first worker.
- */
- private void initWorkers() {
- WorkQueue[] ws, nws; int ps;
- int p = config & SMASK; // find power of two table size
- int n = (p > 1) ? p - 1 : 1; // ensure at least 2 slots
- n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16;
- n = (n + 1) << 1;
- if ((ws = workQueues) == null || ws.length == 0)
- nws = new WorkQueue[n];
- else
- nws = null;
- if (((ps = plock) & PL_LOCK) != 0 ||
- !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
- ps = acquirePlock();
- if (((ws = workQueues) == null || ws.length == 0) && nws != null)
- workQueues = nws;
- int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN);
- if (!U.compareAndSwapInt(this, PLOCK, ps, nps))
- releasePlock(nps);
- tryAddWorker();
- }
-
- /**
* Tries to create and start one worker if fewer than target
* parallelism level exist. Adjusts counts etc on failure.
*/
private void tryAddWorker() {
- long c; int u;
+ long c; int u, e;
while ((u = (int)((c = ctl) >>> 32)) < 0 &&
- (u & SHORT_SIGN) != 0 && (int)c == 0) {
- long nc = (long)(((u + UTC_UNIT) & UTC_MASK) |
- ((u + UAC_UNIT) & UAC_MASK)) << 32;
+ (u & SHORT_SIGN) != 0 && (e = (int)c) >= 0) {
+ long nc = ((long)(((u + UTC_UNIT) & UTC_MASK) |
+ ((u + UAC_UNIT) & UAC_MASK)) << 32) | (long)e;
if (U.compareAndSwapLong(this, CTL, c, nc)) {
ForkJoinWorkerThreadFactory fac;
Throwable ex = null;
@@ -1355,8 +1304,8 @@ public class ForkJoinPool extends AbstractExecutorService {
wt.start();
break;
}
- } catch (Throwable e) {
- ex = e;
+ } catch (Throwable rex) {
+ ex = rex;
}
deregisterWorker(wt, ex);
break;
@@ -1377,14 +1326,14 @@ public class ForkJoinPool extends AbstractExecutorService {
* @return the worker's queue
*/
final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
- Thread.UncaughtExceptionHandler handler; WorkQueue[] ws; int s, ps;
+ UncaughtExceptionHandler handler; WorkQueue[] ws; int s, ps;
wt.setDaemon(true);
if ((handler = ueh) != null)
wt.setUncaughtExceptionHandler(handler);
do {} while (!U.compareAndSwapInt(this, INDEXSEED, s = indexSeed,
s += SEED_INCREMENT) ||
s == 0); // skip 0
- WorkQueue w = new WorkQueue(this, wt, config >>> 16, s);
+ WorkQueue w = new WorkQueue(this, wt, mode, s);
if (((ps = plock) & PL_LOCK) != 0 ||
!U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
ps = acquirePlock();
@@ -1404,14 +1353,15 @@ public class ForkJoinPool extends AbstractExecutorService {
}
}
}
- w.eventCount = w.poolIndex = r; // volatile write orders
+ w.poolIndex = (short)r;
+ w.eventCount = r; // volatile write orders
ws[r] = w;
}
} finally {
if (!U.compareAndSwapInt(this, PLOCK, ps, nps))
releasePlock(nps);
}
- wt.setName(workerNamePrefix.concat(Integer.toString(w.poolIndex)));
+ wt.setName(workerNamePrefix.concat(Integer.toString(w.poolIndex >>> 1)));
return w;
}
@@ -1421,17 +1371,17 @@ public class ForkJoinPool extends AbstractExecutorService {
* array, and adjusts counts. If pool is shutting down, tries to
* complete termination.
*
- * @param wt the worker thread or null if construction failed
+ * @param wt the worker thread, or null if construction failed
* @param ex the exception causing failure, or null if none
*/
final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) {
WorkQueue w = null;
if (wt != null && (w = wt.workQueue) != null) {
- int ps;
+ int ps; long sc;
w.qlock = -1; // ensure set
- long ns = w.nsteals, sc; // collect steal count
do {} while (!U.compareAndSwapLong(this, STEALCOUNT,
- sc = stealCount, sc + ns));
+ sc = stealCount,
+ sc + w.nsteals));
if (((ps = plock) & PL_LOCK) != 0 ||
!U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
ps = acquirePlock();
@@ -1460,7 +1410,7 @@ public class ForkJoinPool extends AbstractExecutorService {
if (e > 0) { // activate or create replacement
if ((ws = workQueues) == null ||
(i = e & SMASK) >= ws.length ||
- (v = ws[i]) != null)
+ (v = ws[i]) == null)
break;
long nc = (((long)(v.nextWait & E_MASK)) |
((long)(u + UAC_UNIT) << 32));
@@ -1489,6 +1439,26 @@ public class ForkJoinPool extends AbstractExecutorService {
// Submissions
/**
+ * Per-thread records for threads that submit to pools. Currently
+ * holds only pseudo-random seed / index that is used to choose
+ * submission queues in method externalPush. In the future, this may
+ * also incorporate a means to implement different task rejection
+ * and resubmission policies.
+ *
+ * Seeds for submitters and workers/workQueues work in basically
+ * the same way but are initialized and updated using slightly
+ * different mechanics. Both are initialized using the same
+ * approach as in class ThreadLocal, where successive values are
+ * unlikely to collide with previous values. Seeds are then
+ * randomly modified upon collisions using xorshifts, which
+ * requires a non-zero seed.
+ */
+ static final class Submitter {
+ int seed;
+ Submitter(int s) { seed = s; }
+ }
+
+ /**
* Unless shutting down, adds the given task to a submission queue
* at submitter's current queue index (modulo submission
* range). Only the most common path is directly handled in this
@@ -1497,19 +1467,21 @@ public class ForkJoinPool extends AbstractExecutorService {
* @param task the task. Caller must ensure non-null.
*/
final void externalPush(ForkJoinTask<?> task) {
- WorkQueue[] ws; WorkQueue q; Submitter z; int m; ForkJoinTask<?>[] a;
- if ((z = submitters.get()) != null && plock > 0 &&
- (ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
- (q = ws[m & z.seed & SQMASK]) != null &&
+ Submitter z = submitters.get();
+ WorkQueue q; int r, m, s, n, am; ForkJoinTask<?>[] a;
+ int ps = plock;
+ WorkQueue[] ws = workQueues;
+ if (z != null && ps > 0 && ws != null && (m = (ws.length - 1)) >= 0 &&
+ (q = ws[m & (r = z.seed) & SQMASK]) != null && r != 0 &&
U.compareAndSwapInt(q, QLOCK, 0, 1)) { // lock
- int b = q.base, s = q.top, n, an;
- if ((a = q.array) != null && (an = a.length) > (n = s + 1 - b)) {
- int j = (((an - 1) & s) << ASHIFT) + ABASE;
+ if ((a = q.array) != null &&
+ (am = a.length - 1) > (n = (s = q.top) - q.base)) {
+ int j = ((am & s) << ASHIFT) + ABASE;
U.putOrderedObject(a, j, task);
q.top = s + 1; // push on to deque
q.qlock = 0;
- if (n <= 2)
- signalWork(q);
+ if (n <= 1)
+ signalWork(ws, q);
return;
}
q.qlock = 0;
@@ -1520,13 +1492,19 @@ public class ForkJoinPool extends AbstractExecutorService {
/**
* Full version of externalPush. This method is called, among
* other times, upon the first submission of the first task to the
- * pool, so must perform secondary initialization (via
- * initWorkers). It also detects first submission by an external
- * thread by looking up its ThreadLocal, and creates a new shared
- * queue if the one at index if empty or contended. The plock lock
- * body must be exception-free (so no try/finally) so we
- * optimistically allocate new queues outside the lock and throw
- * them away if (very rarely) not needed.
+ * pool, so must perform secondary initialization. It also
+ * detects first submission by an external thread by looking up
+ * its ThreadLocal, and creates a new shared queue if the one at
+ * index if empty or contended. The plock lock body must be
+ * exception-free (so no try/finally) so we optimistically
+ * allocate new queues outside the lock and throw them away if
+ * (very rarely) not needed.
+ *
+ * Secondary initialization occurs when plock is zero, to create
+ * workQueue array and set plock to a valid value. This lock body
+ * must also be exception-free. Because the plock seq value can
+ * eventually wrap around zero, this method harmlessly fails to
+ * reinitialize if workQueues exists, while still advancing plock.
*/
private void fullExternalPush(ForkJoinTask<?> task) {
int r = 0; // random index seed
@@ -1537,17 +1515,31 @@ public class ForkJoinPool extends AbstractExecutorService {
r += SEED_INCREMENT) && r != 0)
submitters.set(z = new Submitter(r));
}
- else if (r == 0) { // move to a different index
+ else if (r == 0) { // move to a different index
r = z.seed;
- r ^= r << 13; // same xorshift as WorkQueues
+ r ^= r << 13; // same xorshift as WorkQueues
r ^= r >>> 17;
- z.seed = r ^ (r << 5);
+ z.seed = r ^= (r << 5);
}
- else if ((ps = plock) < 0)
+ if ((ps = plock) < 0)
throw new RejectedExecutionException();
else if (ps == 0 || (ws = workQueues) == null ||
- (m = ws.length - 1) < 0)
- initWorkers();
+ (m = ws.length - 1) < 0) { // initialize workQueues
+ int p = parallelism; // find power of two table size
+ int n = (p > 1) ? p - 1 : 1; // ensure at least 2 slots
+ n |= n >>> 1; n |= n >>> 2; n |= n >>> 4;
+ n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1;
+ WorkQueue[] nws = ((ws = workQueues) == null || ws.length == 0 ?
+ new WorkQueue[n] : null);
+ if (((ps = plock) & PL_LOCK) != 0 ||
+ !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
+ ps = acquirePlock();
+ if (((ws = workQueues) == null || ws.length == 0) && nws != null)
+ workQueues = nws;
+ int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN);
+ if (!U.compareAndSwapInt(this, PLOCK, ps, nps))
+ releasePlock(nps);
+ }
else if ((q = ws[k = r & m & SQMASK]) != null) {
if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {
ForkJoinTask<?>[] a = q.array;
@@ -1565,7 +1557,7 @@ public class ForkJoinPool extends AbstractExecutorService {
q.qlock = 0; // unlock
}
if (submitted) {
- signalWork(q);
+ signalWork(ws, q);
return;
}
}
@@ -1573,6 +1565,7 @@ public class ForkJoinPool extends AbstractExecutorService {
}
else if (((ps = plock) & PL_LOCK) == 0) { // create new queue
q = new WorkQueue(this, null, SHARED_QUEUE, r);
+ q.poolIndex = (short)k;
if (((ps = plock) & PL_LOCK) != 0 ||
!U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
ps = acquirePlock();
@@ -1583,7 +1576,7 @@ public class ForkJoinPool extends AbstractExecutorService {
releasePlock(nps);
}
else
- r = 0; // try elsewhere while lock held
+ r = 0;
}
}
@@ -1594,41 +1587,42 @@ public class ForkJoinPool extends AbstractExecutorService {
*/
final void incrementActiveCount() {
long c;
- do {} while (!U.compareAndSwapLong(this, CTL, c = ctl, c + AC_UNIT));
+ do {} while (!U.compareAndSwapLong
+ (this, CTL, c = ctl, ((c & ~AC_MASK) |
+ ((c & AC_MASK) + AC_UNIT))));
}
/**
* Tries to create or activate a worker if too few are active.
*
- * @param q the (non-null) queue holding tasks to be signalled
+ * @param ws the worker array to use to find signallees
+ * @param q if non-null, the queue holding tasks to be processed
*/
- final void signalWork(WorkQueue q) {
- int hint = q.poolIndex;
- long c; int e, u, i, n; WorkQueue[] ws; WorkQueue w; Thread p;
- while ((u = (int)((c = ctl) >>> 32)) < 0) {
- if ((e = (int)c) > 0) {
- if ((ws = workQueues) != null && ws.length > (i = e & SMASK) &&
- (w = ws[i]) != null && w.eventCount == (e | INT_SIGN)) {
- long nc = (((long)(w.nextWait & E_MASK)) |
- ((long)(u + UAC_UNIT) << 32));
- if (U.compareAndSwapLong(this, CTL, c, nc)) {
- w.hint = hint;
- w.eventCount = (e + E_SEQ) & E_MASK;
- if ((p = w.parker) != null)
- U.unpark(p);
- break;
- }
- if (q.top - q.base <= 0)
- break;
- }
- else
- break;
- }
- else {
+ final void signalWork(WorkQueue[] ws, WorkQueue q) {
+ for (;;) {
+ long c; int e, u, i; WorkQueue w; Thread p;
+ if ((u = (int)((c = ctl) >>> 32)) >= 0)
+ break;
+ if ((e = (int)c) <= 0) {
if ((short)u < 0)
tryAddWorker();
break;
}
+ if (ws == null || ws.length <= (i = e & SMASK) ||
+ (w = ws[i]) == null)
+ break;
+ long nc = (((long)(w.nextWait & E_MASK)) |
+ ((long)(u + UAC_UNIT)) << 32);
+ int ne = (e + E_SEQ) & E_MASK;
+ if (w.eventCount == (e | INT_SIGN) &&
+ U.compareAndSwapLong(this, CTL, c, nc)) {
+ w.eventCount = ne;
+ if ((p = w.parker) != null)
+ U.unpark(p);
+ break;
+ }
+ if (q != null && q.base >= q.top)
+ break;
}
}
@@ -1639,214 +1633,154 @@ public class ForkJoinPool extends AbstractExecutorService {
*/
final void runWorker(WorkQueue w) {
w.growArray(); // allocate queue
- do { w.runTask(scan(w)); } while (w.qlock >= 0);
+ for (int r = w.hint; scan(w, r) == 0; ) {
+ r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
+ }
}
/**
- * Scans for and, if found, returns one task, else possibly
+ * Scans for and, if found, runs one task, else possibly
* inactivates the worker. This method operates on single reads of
* volatile state and is designed to be re-invoked continuously,
* in part because it returns upon detecting inconsistencies,
* contention, or state changes that indicate possible success on
* re-invocation.
*
- * The scan searches for tasks across queues (starting at a random
- * index, and relying on registerWorker to irregularly scatter
- * them within array to avoid bias), checking each at least twice.
- * The scan terminates upon either finding a non-empty queue, or
- * completing the sweep. If the worker is not inactivated, it
- * takes and returns a task from this queue. Otherwise, if not
- * activated, it signals workers (that may include itself) and
- * returns so caller can retry. Also returns for true if the
- * worker array may have changed during an empty scan. On failure
- * to find a task, we take one of the following actions, after
- * which the caller will retry calling this method unless
- * terminated.
- *
- * * If pool is terminating, terminate the worker.
- *
- * * If not already enqueued, try to inactivate and enqueue the
- * worker on wait queue. Or, if inactivating has caused the pool
- * to be quiescent, relay to idleAwaitWork to possibly shrink
- * pool.
- *
- * * If already enqueued and none of the above apply, possibly
- * park awaiting signal, else lingering to help scan and signal.
- *
- * * If a non-empty queue discovered or left as a hint,
- * help wake up other workers before return.
+ * The scan searches for tasks across queues starting at a random
+ * index, checking each at least twice. The scan terminates upon
+ * either finding a non-empty queue, or completing the sweep. If
+ * the worker is not inactivated, it takes and runs a task from
+ * this queue. Otherwise, if not activated, it tries to activate
+ * itself or some other worker by signalling. On failure to find a
+ * task, returns (for retry) if pool state may have changed during
+ * an empty scan, or tries to inactivate if active, else possibly
+ * blocks or terminates via method awaitWork.
*
* @param w the worker (via its WorkQueue)
- * @return a task or null if none found
+ * @param r a random seed
+ * @return worker qlock status if would have waited, else 0
*/
- private final ForkJoinTask<?> scan(WorkQueue w) {
+ private final int scan(WorkQueue w, int r) {
WorkQueue[] ws; int m;
- int ps = plock; // read plock before ws
- if (w != null && (ws = workQueues) != null && (m = ws.length - 1) >= 0) {
- int ec = w.eventCount; // ec is negative if inactive
- int r = w.seed; r ^= r << 13; r ^= r >>> 17; w.seed = r ^= r << 5;
- w.hint = -1; // update seed and clear hint
- int j = ((m + m + 1) | MIN_SCAN) & MAX_SCAN;
- do {
- WorkQueue q; ForkJoinTask<?>[] a; int b;
- if ((q = ws[(r + j) & m]) != null && (b = q.base) - q.top < 0 &&
- (a = q.array) != null) { // probably nonempty
- int i = (((a.length - 1) & b) << ASHIFT) + ABASE;
- ForkJoinTask<?> t = (ForkJoinTask<?>)
- U.getObjectVolatile(a, i);
- if (q.base == b && ec >= 0 && t != null &&
- U.compareAndSwapObject(a, i, t, null)) {
- if ((q.base = b + 1) - q.top < 0)
- signalWork(q);
- return t; // taken
- }
- else if ((ec < 0 || j < m) && (int)(ctl >> AC_SHIFT) <= 0) {
- w.hint = (r + j) & m; // help signal below
- break; // cannot take
+ long c = ctl; // for consistency check
+ if ((ws = workQueues) != null && (m = ws.length - 1) >= 0 && w != null) {
+ for (int j = m + m + 1, ec = w.eventCount;;) {
+ WorkQueue q; int b, e; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
+ if ((q = ws[(r - j) & m]) != null &&
+ (b = q.base) - q.top < 0 && (a = q.array) != null) {
+ long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
+ if ((t = ((ForkJoinTask<?>)
+ U.getObjectVolatile(a, i))) != null) {
+ if (ec < 0)
+ helpRelease(c, ws, w, q, b);
+ else if (q.base == b &&
+ U.compareAndSwapObject(a, i, t, null)) {
+ U.putOrderedInt(q, QBASE, b + 1);
+ if ((b + 1) - q.top < 0)
+ signalWork(ws, q);
+ w.runTask(t);
+ }
}
+ break;
}
- } while (--j >= 0);
-
- int h, e, ns; long c, sc; WorkQueue q;
- if ((ns = w.nsteals) != 0) {
- if (U.compareAndSwapLong(this, STEALCOUNT,
- sc = stealCount, sc + ns))
- w.nsteals = 0; // collect steals and rescan
- }
- else if (plock != ps) // consistency check
- ; // skip
- else if ((e = (int)(c = ctl)) < 0)
- w.qlock = -1; // pool is terminating
- else {
- if ((h = w.hint) < 0) {
- if (ec >= 0) { // try to enqueue/inactivate
- long nc = (((long)ec |
- ((c - AC_UNIT) & (AC_MASK|TC_MASK))));
- w.nextWait = e; // link and mark inactive
+ else if (--j < 0) {
+ if ((ec | (e = (int)c)) < 0) // inactive or terminating
+ return awaitWork(w, c, ec);
+ else if (ctl == c) { // try to inactivate and enqueue
+ long nc = (long)ec | ((c - AC_UNIT) & (AC_MASK|TC_MASK));
+ w.nextWait = e;
w.eventCount = ec | INT_SIGN;
- if (ctl != c || !U.compareAndSwapLong(this, CTL, c, nc))
- w.eventCount = ec; // unmark on CAS failure
- else if ((int)(c >> AC_SHIFT) == 1 - (config & SMASK))
- idleAwaitWork(w, nc, c);
- }
- else if (w.eventCount < 0 && !tryTerminate(false, false) &&
- ctl == c) { // block
- Thread wt = Thread.currentThread();
- Thread.interrupted(); // clear status
- U.putObject(wt, PARKBLOCKER, this);
- w.parker = wt; // emulate LockSupport.park
- if (w.eventCount < 0) // recheck
- U.park(false, 0L);
- w.parker = null;
- U.putObject(wt, PARKBLOCKER, null);
- }
- }
- if ((h >= 0 || (h = w.hint) >= 0) &&
- (ws = workQueues) != null && h < ws.length &&
- (q = ws[h]) != null) { // signal others before retry
- WorkQueue v; Thread p; int u, i, s;
- for (int n = (config & SMASK) >>> 1;;) {
- int idleCount = (w.eventCount < 0) ? 0 : -1;
- if (((s = idleCount - q.base + q.top) <= n &&
- (n = s) <= 0) ||
- (u = (int)((c = ctl) >>> 32)) >= 0 ||
- (e = (int)c) <= 0 || m < (i = e & SMASK) ||
- (v = ws[i]) == null)
- break;
- long nc = (((long)(v.nextWait & E_MASK)) |
- ((long)(u + UAC_UNIT) << 32));
- if (v.eventCount != (e | INT_SIGN) ||
- !U.compareAndSwapLong(this, CTL, c, nc))
- break;
- v.hint = h;
- v.eventCount = (e + E_SEQ) & E_MASK;
- if ((p = v.parker) != null)
- U.unpark(p);
- if (--n <= 0)
- break;
+ if (!U.compareAndSwapLong(this, CTL, c, nc))
+ w.eventCount = ec; // back out
}
+ break;
}
}
}
- return null;
+ return 0;
}
/**
- * If inactivating worker w has caused the pool to become
- * quiescent, checks for pool termination, and, so long as this is
- * not the only worker, waits for event for up to a given
- * duration. On timeout, if ctl has not changed, terminates the
- * worker, which will in turn wake up another worker to possibly
- * repeat this process.
+ * A continuation of scan(), possibly blocking or terminating
+ * worker w. Returns without blocking if pool state has apparently
+ * changed since last invocation. Also, if inactivating w has
+ * caused the pool to become quiescent, checks for pool
+ * termination, and, so long as this is not the only worker, waits
+ * for event for up to a given duration. On timeout, if ctl has
+ * not changed, terminates the worker, which will in turn wake up
+ * another worker to possibly repeat this process.
*
* @param w the calling worker
- * @param currentCtl the ctl value triggering possible quiescence
- * @param prevCtl the ctl value to restore if thread is terminated
- */
- private void idleAwaitWork(WorkQueue w, long currentCtl, long prevCtl) {
- if (w != null && w.eventCount < 0 &&
- !tryTerminate(false, false) && (int)prevCtl != 0) {
- int dc = -(short)(currentCtl >>> TC_SHIFT);
- long parkTime = dc < 0 ? FAST_IDLE_TIMEOUT: (dc + 1) * IDLE_TIMEOUT;
- long deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
- Thread wt = Thread.currentThread();
- while (ctl == currentCtl) {
- Thread.interrupted(); // timed variant of version in scan()
- U.putObject(wt, PARKBLOCKER, this);
- w.parker = wt;
- if (ctl == currentCtl)
- U.park(false, parkTime);
- w.parker = null;
- U.putObject(wt, PARKBLOCKER, null);
- if (ctl != currentCtl)
- break;
- if (deadline - System.nanoTime() <= 0L &&
- U.compareAndSwapLong(this, CTL, currentCtl, prevCtl)) {
- w.eventCount = (w.eventCount + E_SEQ) | E_MASK;
- w.qlock = -1; // shrink
- break;
+ * @param c the ctl value on entry to scan
+ * @param ec the worker's eventCount on entry to scan
+ */
+ private final int awaitWork(WorkQueue w, long c, int ec) {
+ int stat, ns; long parkTime, deadline;
+ if ((stat = w.qlock) >= 0 && w.eventCount == ec && ctl == c &&
+ !Thread.interrupted()) {
+ int e = (int)c;
+ int u = (int)(c >>> 32);
+ int d = (u >> UAC_SHIFT) + parallelism; // active count
+
+ if (e < 0 || (d <= 0 && tryTerminate(false, false)))
+ stat = w.qlock = -1; // pool is terminating
+ else if ((ns = w.nsteals) != 0) { // collect steals and retry
+ long sc;
+ w.nsteals = 0;
+ do {} while (!U.compareAndSwapLong(this, STEALCOUNT,
+ sc = stealCount, sc + ns));
+ }
+ else {
+ long pc = ((d > 0 || ec != (e | INT_SIGN)) ? 0L :
+ ((long)(w.nextWait & E_MASK)) | // ctl to restore
+ ((long)(u + UAC_UNIT)) << 32);
+ if (pc != 0L) { // timed wait if last waiter
+ int dc = -(short)(c >>> TC_SHIFT);
+ parkTime = (dc < 0 ? FAST_IDLE_TIMEOUT:
+ (dc + 1) * IDLE_TIMEOUT);
+ deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
+ }
+ else
+ parkTime = deadline = 0L;
+ if (w.eventCount == ec && ctl == c) {
+ Thread wt = Thread.currentThread();
+ U.putObject(wt, PARKBLOCKER, this);
+ w.parker = wt; // emulate LockSupport.park
+ if (w.eventCount == ec && ctl == c)
+ U.park(false, parkTime); // must recheck before park
+ w.parker = null;
+ U.putObject(wt, PARKBLOCKER, null);
+ if (parkTime != 0L && ctl == c &&
+ deadline - System.nanoTime() <= 0L &&
+ U.compareAndSwapLong(this, CTL, c, pc))
+ stat = w.qlock = -1; // shrink pool
}
}
}
+ return stat;
}
/**
- * Scans through queues looking for work while joining a task; if
- * any present, signals. May return early if more signalling is
- * detectably unneeded.
- *
- * @param task return early if done
- * @param origin an index to start scan
- */
- private void helpSignal(ForkJoinTask<?> task, int origin) {
- WorkQueue[] ws; WorkQueue w; Thread p; long c; int m, u, e, i, s;
- if (task != null && task.status >= 0 &&
- (u = (int)(ctl >>> 32)) < 0 && (u >> UAC_SHIFT) < 0 &&
- (ws = workQueues) != null && (m = ws.length - 1) >= 0) {
- outer: for (int k = origin, j = m; j >= 0; --j) {
- WorkQueue q = ws[k++ & m];
- for (int n = m;;) { // limit to at most m signals
- if (task.status < 0)
- break outer;
- if (q == null ||
- ((s = -q.base + q.top) <= n && (n = s) <= 0))
- break;
- if ((u = (int)((c = ctl) >>> 32)) >= 0 ||
- (e = (int)c) <= 0 || m < (i = e & SMASK) ||
- (w = ws[i]) == null)
- break outer;
- long nc = (((long)(w.nextWait & E_MASK)) |
- ((long)(u + UAC_UNIT) << 32));
- if (w.eventCount != (e | INT_SIGN))
- break outer;
- if (U.compareAndSwapLong(this, CTL, c, nc)) {
- w.eventCount = (e + E_SEQ) & E_MASK;
- if ((p = w.parker) != null)
- U.unpark(p);
- if (--n <= 0)
- break;
- }
- }
+ * Possibly releases (signals) a worker. Called only from scan()
+ * when a worker with apparently inactive status finds a non-empty
+ * queue. This requires revalidating all of the associated state
+ * from caller.
+ */
+ private final void helpRelease(long c, WorkQueue[] ws, WorkQueue w,
+ WorkQueue q, int b) {
+ WorkQueue v; int e, i; Thread p;
+ if (w != null && w.eventCount < 0 && (e = (int)c) > 0 &&
+ ws != null && ws.length > (i = e & SMASK) &&
+ (v = ws[i]) != null && ctl == c) {
+ long nc = (((long)(v.nextWait & E_MASK)) |
+ ((long)((int)(c >>> 32) + UAC_UNIT)) << 32);
+ int ne = (e + E_SEQ) & E_MASK;
+ if (q != null && q.base == b && w.eventCount < 0 &&
+ v.eventCount == (e | INT_SIGN) &&
+ U.compareAndSwapLong(this, CTL, c, nc)) {
+ v.eventCount = ne;
+ if ((p = v.parker) != null)
+ U.unpark(p);
}
}
}
@@ -1871,7 +1805,8 @@ public class ForkJoinPool extends AbstractExecutorService {
*/
private int tryHelpStealer(WorkQueue joiner, ForkJoinTask<?> task) {
int stat = 0, steps = 0; // bound to avoid cycles
- if (joiner != null && task != null) { // hoist null checks
+ if (task != null && joiner != null &&
+ joiner.base - joiner.top >= 0) { // hoist checks
restart: for (;;) {
ForkJoinTask<?> subtask = task; // current target
for (WorkQueue j = joiner, v;;) { // v is stealer of subtask
@@ -1898,7 +1833,7 @@ public class ForkJoinPool extends AbstractExecutorService {
}
}
for (;;) { // help stealer or descend to its stealer
- ForkJoinTask[] a; int b;
+ ForkJoinTask[] a; int b;
if (subtask.status < 0) // surround probes with
continue restart; // consistency checks
if ((b = v.base) - v.top < 0 && (a = v.array) != null) {
@@ -1909,13 +1844,23 @@ public class ForkJoinPool extends AbstractExecutorService {
v.currentSteal != subtask)
continue restart; // stale
stat = 1; // apparent progress
- if (t != null && v.base == b &&
- U.compareAndSwapObject(a, i, t, null)) {
- v.base = b + 1; // help stealer
- joiner.runSubtask(t);
+ if (v.base == b) {
+ if (t == null)
+ break restart;
+ if (U.compareAndSwapObject(a, i, t, null)) {
+ U.putOrderedInt(v, QBASE, b + 1);
+ ForkJoinTask<?> ps = joiner.currentSteal;
+ int jt = joiner.top;
+ do {
+ joiner.currentSteal = t;
+ t.doExec(); // clear local tasks too
+ } while (task.status >= 0 &&
+ joiner.top != jt &&
+ (t = joiner.pop()) != null);
+ joiner.currentSteal = ps;
+ break restart;
+ }
}
- else if (v.base == b && ++steps == MAX_HELP)
- break restart; // v apparently stalled
}
else { // empty -- try to descend
ForkJoinTask<?> next = v.currentJoin;
@@ -1942,27 +1887,33 @@ public class ForkJoinPool extends AbstractExecutorService {
* and run tasks within the target's computation.
*
* @param task the task to join
- * @param mode if shared, exit upon completing any task
- * if all workers are active
- */
- private int helpComplete(ForkJoinTask<?> task, int mode) {
- WorkQueue[] ws; WorkQueue q; int m, n, s, u;
- if (task != null && (ws = workQueues) != null &&
- (m = ws.length - 1) >= 0) {
- for (int j = 1, origin = j;;) {
+ */
+ private int helpComplete(WorkQueue joiner, CountedCompleter<?> task) {
+ WorkQueue[] ws; int m;
+ int s = 0;
+ if ((ws = workQueues) != null && (m = ws.length - 1) >= 0 &&
+ joiner != null && task != null) {
+ int j = joiner.poolIndex;
+ int scans = m + m + 1;
+ long c = 0L; // for stability check
+ for (int k = scans; ; j += 2) {
+ WorkQueue q;
if ((s = task.status) < 0)
- return s;
- if ((q = ws[j & m]) != null && q.pollAndExecCC(task)) {
- origin = j;
- if (mode == SHARED_QUEUE &&
- ((u = (int)(ctl >>> 32)) >= 0 || (u >> UAC_SHIFT) >= 0))
+ break;
+ else if (joiner.internalPopAndExecCC(task))
+ k = scans;
+ else if ((s = task.status) < 0)
+ break;
+ else if ((q = ws[j & m]) != null && q.pollAndExecCC(task))
+ k = scans;
+ else if (--k < 0) {
+ if (c == (c = ctl))
break;
+ k = scans;
}
- else if ((j = (j + 2) & m) == origin)
- break;
}
}
- return 0;
+ return s;
}
/**
@@ -1971,17 +1922,22 @@ public class ForkJoinPool extends AbstractExecutorService {
* for blocking. Fails on contention or termination. Otherwise,
* adds a new thread if no idle workers are available and pool
* may become starved.
+ *
+ * @param c the assumed ctl value
*/
- final boolean tryCompensate() {
- int pc = config & SMASK, e, i, tc; long c;
- WorkQueue[] ws; WorkQueue w; Thread p;
- if ((ws = workQueues) != null && (e = (int)(c = ctl)) >= 0) {
- if (e != 0 && (i = e & SMASK) < ws.length &&
- (w = ws[i]) != null && w.eventCount == (e | INT_SIGN)) {
+ final boolean tryCompensate(long c) {
+ WorkQueue[] ws = workQueues;
+ int pc = parallelism, e = (int)c, m, tc;
+ if (ws != null && (m = ws.length - 1) >= 0 && e >= 0 && ctl == c) {
+ WorkQueue w = ws[e & m];
+ if (e != 0 && w != null) {
+ Thread p;
long nc = ((long)(w.nextWait & E_MASK) |
(c & (AC_MASK|TC_MASK)));
- if (U.compareAndSwapLong(this, CTL, c, nc)) {
- w.eventCount = (e + E_SEQ) & E_MASK;
+ int ne = (e + E_SEQ) & E_MASK;
+ if (w.eventCount == (e | INT_SIGN) &&
+ U.compareAndSwapLong(this, CTL, c, nc)) {
+ w.eventCount = ne;
if ((p = w.parker) != null)
U.unpark(p);
return true; // replace with idle worker
@@ -2024,23 +1980,20 @@ public class ForkJoinPool extends AbstractExecutorService {
*/
final int awaitJoin(WorkQueue joiner, ForkJoinTask<?> task) {
int s = 0;
- if (joiner != null && task != null && (s = task.status) >= 0) {
+ if (task != null && (s = task.status) >= 0 && joiner != null) {
ForkJoinTask<?> prevJoin = joiner.currentJoin;
joiner.currentJoin = task;
- do {} while ((s = task.status) >= 0 && !joiner.isEmpty() &&
- joiner.tryRemoveAndExec(task)); // process local tasks
- if (s >= 0 && (s = task.status) >= 0) {
- helpSignal(task, joiner.poolIndex);
- if ((s = task.status) >= 0 &&
- (task instanceof CountedCompleter))
- s = helpComplete(task, LIFO_QUEUE);
- }
+ do {} while (joiner.tryRemoveAndExec(task) && // process local tasks
+ (s = task.status) >= 0);
+ if (s >= 0 && (task instanceof CountedCompleter))
+ s = helpComplete(joiner, (CountedCompleter<?>)task);
+ long cc = 0; // for stability checks
while (s >= 0 && (s = task.status) >= 0) {
- if ((!joiner.isEmpty() || // try helping
- (s = tryHelpStealer(joiner, task)) == 0) &&
+ if ((s = tryHelpStealer(joiner, task)) == 0 &&
(s = task.status) >= 0) {
- helpSignal(task, joiner.poolIndex);
- if ((s = task.status) >= 0 && tryCompensate()) {
+ if (!tryCompensate(cc))
+ cc = ctl;
+ else {
if (task.trySetSignal() && (s = task.status) >= 0) {
synchronized (task) {
if (task.status >= 0) {
@@ -2053,9 +2006,11 @@ public class ForkJoinPool extends AbstractExecutorService {
task.notifyAll();
}
}
- long c; // re-activate
+ long c; // reactivate
do {} while (!U.compareAndSwapLong
- (this, CTL, c = ctl, c + AC_UNIT));
+ (this, CTL, c = ctl,
+ ((c & ~AC_MASK) |
+ ((c & AC_MASK) + AC_UNIT))));
}
}
}
@@ -2077,15 +2032,11 @@ public class ForkJoinPool extends AbstractExecutorService {
if (joiner != null && task != null && (s = task.status) >= 0) {
ForkJoinTask<?> prevJoin = joiner.currentJoin;
joiner.currentJoin = task;
- do {} while ((s = task.status) >= 0 && !joiner.isEmpty() &&
- joiner.tryRemoveAndExec(task));
- if (s >= 0 && (s = task.status) >= 0) {
- helpSignal(task, joiner.poolIndex);
- if ((s = task.status) >= 0 &&
- (task instanceof CountedCompleter))
- s = helpComplete(task, LIFO_QUEUE);
- }
- if (s >= 0 && joiner.isEmpty()) {
+ do {} while (joiner.tryRemoveAndExec(task) && // process local tasks
+ (s = task.status) >= 0);
+ if (s >= 0) {
+ if (task instanceof CountedCompleter)
+ helpComplete(joiner, (CountedCompleter<?>)task);
do {} while (task.status >= 0 &&
tryHelpStealer(joiner, task) > 0);
}
@@ -2095,29 +2046,22 @@ public class ForkJoinPool extends AbstractExecutorService {
/**
* Returns a (probably) non-empty steal queue, if one is found
- * during a random, then cyclic scan, else null. This method must
- * be retried by caller if, by the time it tries to use the queue,
- * it is empty.
- * @param r a (random) seed for scanning
- */
- private WorkQueue findNonEmptyStealQueue(int r) {
- for (WorkQueue[] ws;;) {
- int ps = plock, m, n;
- if ((ws = workQueues) == null || (m = ws.length - 1) < 1)
- return null;
- for (int j = (m + 1) << 2; ;) {
- WorkQueue q = ws[(((r + j) << 1) | 1) & m];
- if (q != null && (n = q.base - q.top) < 0) {
- if (n < -1)
- signalWork(q);
- return q;
- }
- else if (--j < 0) {
- if (plock == ps)
- return null;
- break;
+ * during a scan, else null. This method must be retried by
+ * caller if, by the time it tries to use the queue, it is empty.
+ */
+ private WorkQueue findNonEmptyStealQueue() {
+ int r = ThreadLocalRandom.current().nextInt();
+ for (;;) {
+ int ps = plock, m; WorkQueue[] ws; WorkQueue q;
+ if ((ws = workQueues) != null && (m = ws.length - 1) >= 0) {
+ for (int j = (m + 1) << 2; j >= 0; --j) {
+ if ((q = ws[(((r - j) << 1) | 1) & m]) != null &&
+ q.base - q.top < 0)
+ return q;
}
}
+ if (plock == ps)
+ return null;
}
}
@@ -2128,38 +2072,36 @@ public class ForkJoinPool extends AbstractExecutorService {
* find tasks either.
*/
final void helpQuiescePool(WorkQueue w) {
+ ForkJoinTask<?> ps = w.currentSteal;
for (boolean active = true;;) {
- ForkJoinTask<?> localTask; // exhaust local queue
- while ((localTask = w.nextLocalTask()) != null)
- localTask.doExec();
- // Similar to loop in scan(), but ignoring submissions
- WorkQueue q = findNonEmptyStealQueue(w.nextSeed());
- if (q != null) {
- ForkJoinTask<?> t; int b;
+ long c; WorkQueue q; ForkJoinTask<?> t; int b;
+ while ((t = w.nextLocalTask()) != null)
+ t.doExec();
+ if ((q = findNonEmptyStealQueue()) != null) {
if (!active) { // re-establish active count
- long c;
active = true;
do {} while (!U.compareAndSwapLong
- (this, CTL, c = ctl, c + AC_UNIT));
+ (this, CTL, c = ctl,
+ ((c & ~AC_MASK) |
+ ((c & AC_MASK) + AC_UNIT))));
+ }
+ if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null) {
+ (w.currentSteal = t).doExec();
+ w.currentSteal = ps;
}
- if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null)
- w.runSubtask(t);
}
- else {
- long c;
- if (active) { // decrement active count without queuing
+ else if (active) { // decrement active count without queuing
+ long nc = ((c = ctl) & ~AC_MASK) | ((c & AC_MASK) - AC_UNIT);
+ if ((int)(nc >> AC_SHIFT) + parallelism == 0)
+ break; // bypass decrement-then-increment
+ if (U.compareAndSwapLong(this, CTL, c, nc))
active = false;
- do {} while (!U.compareAndSwapLong
- (this, CTL, c = ctl, c -= AC_UNIT));
- }
- else
- c = ctl; // re-increment on exit
- if ((int)(c >> AC_SHIFT) + (config & SMASK) == 0) {
- do {} while (!U.compareAndSwapLong
- (this, CTL, c = ctl, c + AC_UNIT));
- break;
- }
}
+ else if ((int)((c = ctl) >> AC_SHIFT) + parallelism <= 0 &&
+ U.compareAndSwapLong
+ (this, CTL, c, ((c & ~AC_MASK) |
+ ((c & AC_MASK) + AC_UNIT))))
+ break;
}
}
@@ -2173,7 +2115,7 @@ public class ForkJoinPool extends AbstractExecutorService {
WorkQueue q; int b;
if ((t = w.nextLocalTask()) != null)
return t;
- if ((q = findNonEmptyStealQueue(w.nextSeed())) == null)
+ if ((q = findNonEmptyStealQueue()) == null)
return null;
if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null)
return t;
@@ -2229,7 +2171,7 @@ public class ForkJoinPool extends AbstractExecutorService {
static int getSurplusQueuedTaskCount() {
Thread t; ForkJoinWorkerThread wt; ForkJoinPool pool; WorkQueue q;
if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)) {
- int p = (pool = (wt = (ForkJoinWorkerThread)t).pool).config & SMASK;
+ int p = (pool = (wt = (ForkJoinWorkerThread)t).pool).parallelism;
int n = (q = wt.workQueue).top - q.base;
int a = (int)(pool.ctl >> AC_SHIFT) + p;
return n - (a > (p >>>= 1) ? 0 :
@@ -2258,45 +2200,47 @@ public class ForkJoinPool extends AbstractExecutorService {
* @return true if now terminating or terminated
*/
private boolean tryTerminate(boolean now, boolean enable) {
- if (this == commonPool) // cannot shut down
+ int ps;
+ if (this == common) // cannot shut down
return false;
+ if ((ps = plock) >= 0) { // enable by setting plock
+ if (!enable)
+ return false;
+ if ((ps & PL_LOCK) != 0 ||
+ !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
+ ps = acquirePlock();
+ int nps = ((ps + PL_LOCK) & ~SHUTDOWN) | SHUTDOWN;
+ if (!U.compareAndSwapInt(this, PLOCK, ps, nps))
+ releasePlock(nps);
+ }
for (long c;;) {
- if (((c = ctl) & STOP_BIT) != 0) { // already terminating
- if ((short)(c >>> TC_SHIFT) == -(config & SMASK)) {
+ if (((c = ctl) & STOP_BIT) != 0) { // already terminating
+ if ((short)(c >>> TC_SHIFT) + parallelism <= 0) {
synchronized (this) {
- notifyAll(); // signal when 0 workers
+ notifyAll(); // signal when 0 workers
}
}
return true;
}
- if (plock >= 0) { // not yet enabled
- int ps;
- if (!enable)
- return false;
- if (((ps = plock) & PL_LOCK) != 0 ||
- !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
- ps = acquirePlock();
- if (!U.compareAndSwapInt(this, PLOCK, ps, SHUTDOWN))
- releasePlock(SHUTDOWN);
- }
- if (!now) { // check if idle & no tasks
- if ((int)(c >> AC_SHIFT) != -(config & SMASK) ||
- hasQueuedSubmissions())
+ if (!now) { // check if idle & no tasks
+ WorkQueue[] ws; WorkQueue w;
+ if ((int)(c >> AC_SHIFT) + parallelism > 0)
return false;
- // Check for unqueued inactive workers. One pass suffices.
- WorkQueue[] ws = workQueues; WorkQueue w;
- if (ws != null) {
- for (int i = 1; i < ws.length; i += 2) {
- if ((w = ws[i]) != null && w.eventCount >= 0)
+ if ((ws = workQueues) != null) {
+ for (int i = 0; i < ws.length; ++i) {
+ if ((w = ws[i]) != null &&
+ (!w.isEmpty() ||
+ ((i & 1) != 0 && w.eventCount >= 0))) {
+ signalWork(ws, w);
return false;
+ }
}
}
}
if (U.compareAndSwapLong(this, CTL, c, c | STOP_BIT)) {
for (int pass = 0; pass < 3; ++pass) {
- WorkQueue[] ws = workQueues;
- if (ws != null) {
- WorkQueue w; Thread wt;
+ WorkQueue[] ws; WorkQueue w; Thread wt;
+ if ((ws = workQueues) != null) {
int n = ws.length;
for (int i = 0; i < n; ++i) {
if ((w = ws[i]) != null) {
@@ -2307,7 +2251,7 @@ public class ForkJoinPool extends AbstractExecutorService {
if (!wt.isInterrupted()) {
try {
wt.interrupt();
- } catch (SecurityException ignore) {
+ } catch (Throwable ignore) {
}
}
U.unpark(wt);
@@ -2318,7 +2262,7 @@ public class ForkJoinPool extends AbstractExecutorService {
// Wake up workers parked on event queue
int i, e; long cc; Thread p;
while ((e = (int)(cc = ctl) & E_MASK) != 0 &&
- (i = e & SMASK) < n &&
+ (i = e & SMASK) < n && i >= 0 &&
(w = ws[i]) != null) {
long nc = ((long)(w.nextWait & E_MASK) |
((cc + AC_UNIT) & AC_MASK) |
@@ -2344,9 +2288,9 @@ public class ForkJoinPool extends AbstractExecutorService {
* least one task.
*/
static WorkQueue commonSubmitterQueue() {
- ForkJoinPool p; WorkQueue[] ws; int m; Submitter z;
+ Submitter z; ForkJoinPool p; WorkQueue[] ws; int m, r;
return ((z = submitters.get()) != null &&
- (p = commonPool) != null &&
+ (p = common) != null &&
(ws = p.workQueues) != null &&
(m = ws.length - 1) >= 0) ?
ws[m & z.seed & SQMASK] : null;
@@ -2355,127 +2299,57 @@ public class ForkJoinPool extends AbstractExecutorService {
/**
* Tries to pop the given task from submitter's queue in common pool.
*/
- static boolean tryExternalUnpush(ForkJoinTask<?> t) {
- ForkJoinPool p; WorkQueue[] ws; WorkQueue q; Submitter z;
- ForkJoinTask<?>[] a; int m, s;
- if (t != null &&
- (z = submitters.get()) != null &&
- (p = commonPool) != null &&
- (ws = p.workQueues) != null &&
- (m = ws.length - 1) >= 0 &&
- (q = ws[m & z.seed & SQMASK]) != null &&
- (s = q.top) != q.base &&
- (a = q.array) != null) {
+ final boolean tryExternalUnpush(ForkJoinTask<?> task) {
+ WorkQueue joiner; ForkJoinTask<?>[] a; int m, s;
+ Submitter z = submitters.get();
+ WorkQueue[] ws = workQueues;
+ boolean popped = false;
+ if (z != null && ws != null && (m = ws.length - 1) >= 0 &&
+ (joiner = ws[z.seed & m & SQMASK]) != null &&
+ joiner.base != (s = joiner.top) &&
+ (a = joiner.array) != null) {
long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE;
- if (U.getObject(a, j) == t &&
- U.compareAndSwapInt(q, QLOCK, 0, 1)) {
- if (q.array == a && q.top == s && // recheck
- U.compareAndSwapObject(a, j, t, null)) {
- q.top = s - 1;
- q.qlock = 0;
- return true;
+ if (U.getObject(a, j) == task &&
+ U.compareAndSwapInt(joiner, QLOCK, 0, 1)) {
+ if (joiner.top == s && joiner.array == a &&
+ U.compareAndSwapObject(a, j, task, null)) {
+ joiner.top = s - 1;
+ popped = true;
}
- q.qlock = 0;
+ joiner.qlock = 0;
}
}
- return false;
+ return popped;
}
- /**
- * Tries to pop and run local tasks within the same computation
- * as the given root. On failure, tries to help complete from
- * other queues via helpComplete.
- */
- private void externalHelpComplete(WorkQueue q, ForkJoinTask<?> root) {
- ForkJoinTask<?>[] a; int m;
- if (q != null && (a = q.array) != null && (m = (a.length - 1)) >= 0 &&
- root != null && root.status >= 0) {
- for (;;) {
- int s, u; Object o; CountedCompleter<?> task = null;
- if ((s = q.top) - q.base > 0) {
- long j = ((m & (s - 1)) << ASHIFT) + ABASE;
- if ((o = U.getObject(a, j)) != null &&
- (o instanceof CountedCompleter)) {
- CountedCompleter<?> t = (CountedCompleter<?>)o, r = t;
- do {
- if (r == root) {
- if (U.compareAndSwapInt(q, QLOCK, 0, 1)) {
- if (q.array == a && q.top == s &&
- U.compareAndSwapObject(a, j, t, null)) {
- q.top = s - 1;
- task = t;
- }
- q.qlock = 0;
- }
- break;
- }
- } while ((r = r.completer) != null);
- }
- }
- if (task != null)
- task.doExec();
- if (root.status < 0 ||
- (u = (int)(ctl >>> 32)) >= 0 || (u >> UAC_SHIFT) >= 0)
+ final int externalHelpComplete(CountedCompleter<?> task) {
+ WorkQueue joiner; int m, j;
+ Submitter z = submitters.get();
+ WorkQueue[] ws = workQueues;
+ int s = 0;
+ if (z != null && ws != null && (m = ws.length - 1) >= 0 &&
+ (joiner = ws[(j = z.seed) & m & SQMASK]) != null && task != null) {
+ int scans = m + m + 1;
+ long c = 0L; // for stability check
+ j |= 1; // poll odd queues
+ for (int k = scans; ; j += 2) {
+ WorkQueue q;
+ if ((s = task.status) < 0)
break;
- if (task == null) {
- helpSignal(root, q.poolIndex);
- if (root.status >= 0)
- helpComplete(root, SHARED_QUEUE);
+ else if (joiner.externalPopAndExecCC(task))
+ k = scans;
+ else if ((s = task.status) < 0)
break;
+ else if ((q = ws[j & m]) != null && q.pollAndExecCC(task))
+ k = scans;
+ else if (--k < 0) {
+ if (c == (c = ctl))
+ break;
+ k = scans;
}
}
}
- }
-
- /**
- * Tries to help execute or signal availability of the given task
- * from submitter's queue in common pool.
- */
- static void externalHelpJoin(ForkJoinTask<?> t) {
- // Some hard-to-avoid overlap with tryExternalUnpush
- ForkJoinPool p; WorkQueue[] ws; WorkQueue q, w; Submitter z;
- ForkJoinTask<?>[] a; int m, s, n;
- if (t != null &&
- (z = submitters.get()) != null &&
- (p = commonPool) != null &&
- (ws = p.workQueues) != null &&
- (m = ws.length - 1) >= 0 &&
- (q = ws[m & z.seed & SQMASK]) != null &&
- (a = q.array) != null) {
- int am = a.length - 1;
- if ((s = q.top) != q.base) {
- long j = ((am & (s - 1)) << ASHIFT) + ABASE;
- if (U.getObject(a, j) == t &&
- U.compareAndSwapInt(q, QLOCK, 0, 1)) {
- if (q.array == a && q.top == s &&
- U.compareAndSwapObject(a, j, t, null)) {
- q.top = s - 1;
- q.qlock = 0;
- t.doExec();
- }
- else
- q.qlock = 0;
- }
- }
- if (t.status >= 0) {
- if (t instanceof CountedCompleter)
- p.externalHelpComplete(q, t);
- else
- p.helpSignal(t, q.poolIndex);
- }
- }
- }
-
- /**
- * Restricted version of helpQuiescePool for external callers
- */
- static void externalHelpQuiescePool() {
- ForkJoinPool p; ForkJoinTask<?> t; WorkQueue q; int b;
- if ((p = commonPool) != null &&
- (q = p.findNonEmptyStealQueue(1)) != null &&
- (b = q.base) - q.top < 0 &&
- (t = q.pollAt(b)) != null)
- t.doExec();
+ return s;
}
// Exported methods
@@ -2529,49 +2403,65 @@ public class ForkJoinPool extends AbstractExecutorService {
*/
public ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
- Thread.UncaughtExceptionHandler handler,
+ UncaughtExceptionHandler handler,
boolean asyncMode) {
+ this(checkParallelism(parallelism),
+ checkFactory(factory),
+ handler,
+ (asyncMode ? FIFO_QUEUE : LIFO_QUEUE),
+ "ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
- if (factory == null)
- throw new NullPointerException();
+ }
+
+ private static int checkParallelism(int parallelism) {
if (parallelism <= 0 || parallelism > MAX_CAP)
throw new IllegalArgumentException();
- this.factory = factory;
- this.ueh = handler;
- this.config = parallelism | (asyncMode ? (FIFO_QUEUE << 16) : 0);
- long np = (long)(-parallelism); // offset ctl counts
- this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
- int pn = nextPoolId();
- StringBuilder sb = new StringBuilder("ForkJoinPool-");
- sb.append(Integer.toString(pn));
- sb.append("-worker-");
- this.workerNamePrefix = sb.toString();
+ return parallelism;
+ }
+
+ private static ForkJoinWorkerThreadFactory checkFactory
+ (ForkJoinWorkerThreadFactory factory) {
+ if (factory == null)
+ throw new NullPointerException();
+ return factory;
}
/**
- * Constructor for common pool, suitable only for static initialization.
- * Basically the same as above, but uses smallest possible initial footprint.
+ * Creates a {@code ForkJoinPool} with the given parameters, without
+ * any security checks or parameter validation. Invoked directly by
+ * makeCommonPool.
*/
- ForkJoinPool(int parallelism, long ctl,
- ForkJoinWorkerThreadFactory factory,
- Thread.UncaughtExceptionHandler handler) {
- this.config = parallelism;
- this.ctl = ctl;
+ private ForkJoinPool(int parallelism,
+ ForkJoinWorkerThreadFactory factory,
+ UncaughtExceptionHandler handler,
+ int mode,
+ String workerNamePrefix) {
+ this.workerNamePrefix = workerNamePrefix;
this.factory = factory;
this.ueh = handler;
- this.workerNamePrefix = "ForkJoinPool.commonPool-worker-";
+ this.mode = (short)mode;
+ this.parallelism = (short)parallelism;
+ long np = (long)(-parallelism); // offset ctl counts
+ this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}
/**
- * Returns the common pool instance.
+ * Returns the common pool instance. This pool is statically
+ * constructed; its run state is unaffected by attempts to {@link
+ * #shutdown} or {@link #shutdownNow}. However this pool and any
+ * ongoing processing are automatically terminated upon program
+ * {@link System#exit}. Any program that relies on asynchronous
+ * task processing to complete before program termination should
+ * invoke {@code commonPool().}{@link #awaitQuiescence awaitQuiescence},
+ * before exit.
*
* @return the common pool instance
* @since 1.8
* @hide
*/
public static ForkJoinPool commonPool() {
- // assert commonPool != null : "static init error";
- return commonPool;
+ // assert common != null : "static init error";
+ return common;
}
// Execution methods
@@ -2627,7 +2517,7 @@ public class ForkJoinPool extends AbstractExecutorService {
if (task instanceof ForkJoinTask<?>) // avoid re-wrap
job = (ForkJoinTask<?>) task;
else
- job = new ForkJoinTask.AdaptedRunnableAction(task);
+ job = new ForkJoinTask.RunnableExecuteAction(task);
externalPush(job);
}
@@ -2729,7 +2619,7 @@ public class ForkJoinPool extends AbstractExecutorService {
*
* @return the handler, or {@code null} if none
*/
- public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() {
+ public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return ueh;
}
@@ -2739,7 +2629,8 @@ public class ForkJoinPool extends AbstractExecutorService {
* @return the targeted parallelism level of this pool
*/
public int getParallelism() {
- return config & SMASK;
+ int par;
+ return ((par = parallelism) > 0) ? par : 1;
}
/**
@@ -2750,7 +2641,7 @@ public class ForkJoinPool extends AbstractExecutorService {
* @hide
*/
public static int getCommonPoolParallelism() {
- return commonPoolParallelism;
+ return commonParallelism;
}
/**
@@ -2762,7 +2653,7 @@ public class ForkJoinPool extends AbstractExecutorService {
* @return the number of worker threads
*/
public int getPoolSize() {
- return (config & SMASK) + (short)(ctl >>> TC_SHIFT);
+ return parallelism + (short)(ctl >>> TC_SHIFT);
}
/**
@@ -2772,7 +2663,7 @@ public class ForkJoinPool extends AbstractExecutorService {
* @return {@code true} if this pool uses async mode
*/
public boolean getAsyncMode() {
- return (config >>> 16) == FIFO_QUEUE;
+ return mode == FIFO_QUEUE;
}
/**
@@ -2803,7 +2694,7 @@ public class ForkJoinPool extends AbstractExecutorService {
* @return the number of active threads
*/
public int getActiveThreadCount() {
- int r = (config & SMASK) + (int)(ctl >> AC_SHIFT);
+ int r = parallelism + (int)(ctl >> AC_SHIFT);
return (r <= 0) ? 0 : r; // suppress momentarily negative values
}
@@ -2819,7 +2710,7 @@ public class ForkJoinPool extends AbstractExecutorService {
* @return {@code true} if all threads are currently idle
*/
public boolean isQuiescent() {
- return (int)(ctl >> AC_SHIFT) + (config & SMASK) == 0;
+ return parallelism + (int)(ctl >> AC_SHIFT) <= 0;
}
/**
@@ -2982,7 +2873,7 @@ public class ForkJoinPool extends AbstractExecutorService {
}
}
}
- int pc = (config & SMASK);
+ int pc = parallelism;
int tc = pc + (short)(c >>> TC_SHIFT);
int ac = pc + (int)(c >> AC_SHIFT);
if (ac < 0) // ignore transient negative
@@ -3008,15 +2899,10 @@ public class ForkJoinPool extends AbstractExecutorService {
* Possibly initiates an orderly shutdown in which previously
* submitted tasks are executed, but no new tasks will be
* accepted. Invocation has no effect on execution state if this
- * is the {@link #commonPool()}, and no additional effect if
+ * is the {@code commonPool()}, and no additional effect if
* already shut down. Tasks that are in the process of being
* submitted concurrently during the course of this method may or
* may not be rejected.
- *
- * @throws SecurityException if a security manager exists and
- * the caller is not permitted to modify threads
- * because it does not hold {@link
- * java.lang.RuntimePermission}{@code ("modifyThread")}
*/
public void shutdown() {
checkPermission();
@@ -3026,7 +2912,7 @@ public class ForkJoinPool extends AbstractExecutorService {
/**
* Possibly attempts to cancel and/or stop all tasks, and reject
* all subsequently submitted tasks. Invocation has no effect on
- * execution state if this is the {@link #commonPool()}, and no
+ * execution state if this is the {@code commonPool()}, and no
* additional effect if already shut down. Otherwise, tasks that
* are in the process of being submitted or executed concurrently
* during the course of this method may or may not be
@@ -3051,7 +2937,7 @@ public class ForkJoinPool extends AbstractExecutorService {
public boolean isTerminated() {
long c = ctl;
return ((c & STOP_BIT) != 0L &&
- (short)(c >>> TC_SHIFT) == -(config & SMASK));
+ (short)(c >>> TC_SHIFT) + parallelism <= 0);
}
/**
@@ -3070,7 +2956,7 @@ public class ForkJoinPool extends AbstractExecutorService {
public boolean isTerminating() {
long c = ctl;
return ((c & STOP_BIT) != 0L &&
- (short)(c >>> TC_SHIFT) != -(config & SMASK));
+ (short)(c >>> TC_SHIFT) + parallelism > 0);
}
/**
@@ -3085,9 +2971,10 @@ public class ForkJoinPool extends AbstractExecutorService {
/**
* Blocks until all tasks have completed execution after a
* shutdown request, or the timeout occurs, or the current thread
- * is interrupted, whichever happens first. Note that the {@link
- * #commonPool()} never terminates until program shutdown so
- * this method will always time out.
+ * is interrupted, whichever happens first. Because the {@code
+ * commonPool()} never terminates until program shutdown, when
+ * applied to the common pool, this method is equivalent to {@link
+ * #awaitQuiescence(long, TimeUnit)} but always returns {@code false}.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
@@ -3097,6 +2984,12 @@ public class ForkJoinPool extends AbstractExecutorService {
*/
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
+ if (Thread.interrupted())
+ throw new InterruptedException();
+ if (this == common) {
+ awaitQuiescence(timeout, unit);
+ return false;
+ }
long nanos = unit.toNanos(timeout);
if (isTerminated())
return true;
@@ -3117,6 +3010,59 @@ public class ForkJoinPool extends AbstractExecutorService {
}
/**
+ * If called by a ForkJoinTask operating in this pool, equivalent
+ * in effect to {@link ForkJoinTask#helpQuiesce}. Otherwise,
+ * waits and/or attempts to assist performing tasks until this
+ * pool {@link #isQuiescent} or the indicated timeout elapses.
+ *
+ * @param timeout the maximum time to wait
+ * @param unit the time unit of the timeout argument
+ * @return {@code true} if quiescent; {@code false} if the
+ * timeout elapsed.
+ */
+ public boolean awaitQuiescence(long timeout, TimeUnit unit) {
+ long nanos = unit.toNanos(timeout);
+ ForkJoinWorkerThread wt;
+ Thread thread = Thread.currentThread();
+ if ((thread instanceof ForkJoinWorkerThread) &&
+ (wt = (ForkJoinWorkerThread)thread).pool == this) {
+ helpQuiescePool(wt.workQueue);
+ return true;
+ }
+ long startTime = System.nanoTime();
+ WorkQueue[] ws;
+ int r = 0, m;
+ boolean found = true;
+ while (!isQuiescent() && (ws = workQueues) != null &&
+ (m = ws.length - 1) >= 0) {
+ if (!found) {
+ if ((System.nanoTime() - startTime) > nanos)
+ return false;
+ Thread.yield(); // cannot block
+ }
+ found = false;
+ for (int j = (m + 1) << 2; j >= 0; --j) {
+ ForkJoinTask<?> t; WorkQueue q; int b;
+ if ((q = ws[r++ & m]) != null && (b = q.base) - q.top < 0) {
+ found = true;
+ if ((t = q.pollAt(b)) != null)
+ t.doExec();
+ break;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Waits and/or attempts to assist performing tasks indefinitely
+ * until the {@code commonPool()} {@link #isQuiescent}.
+ */
+ static void quiesceCommonPool() {
+ common.awaitQuiescence(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
+ }
+
+ /**
* Interface for extending managed parallelism for tasks running
* in {@link ForkJoinPool}s.
*
@@ -3125,9 +3071,9 @@ public class ForkJoinPool extends AbstractExecutorService {
* not necessary. Method {@code block} blocks the current thread
* if necessary (perhaps internally invoking {@code isReleasable}
* before actually blocking). These actions are performed by any
- * thread invoking {@link ForkJoinPool#managedBlock}. The
- * unusual methods in this API accommodate synchronizers that may,
- * but don't usually, block for long periods. Similarly, they
+ * thread invoking {@link ForkJoinPool#managedBlock(ManagedBlocker)}.
+ * The unusual methods in this API accommodate synchronizers that
+ * may, but don't usually, block for long periods. Similarly, they
* allow more efficient internal handling of cases in which
* additional workers may be, but usually are not, needed to
* ensure sufficient parallelism. Toward this end,
@@ -3185,6 +3131,7 @@ public class ForkJoinPool extends AbstractExecutorService {
/**
* Returns {@code true} if blocking is unnecessary.
+ * @return {@code true} if blocking is unnecessary
*/
boolean isReleasable();
}
@@ -3214,21 +3161,8 @@ public class ForkJoinPool extends AbstractExecutorService {
Thread t = Thread.currentThread();
if (t instanceof ForkJoinWorkerThread) {
ForkJoinPool p = ((ForkJoinWorkerThread)t).pool;
- while (!blocker.isReleasable()) { // variant of helpSignal
- WorkQueue[] ws; WorkQueue q; int m, u;
- if ((ws = p.workQueues) != null && (m = ws.length - 1) >= 0) {
- for (int i = 0; i <= m; ++i) {
- if (blocker.isReleasable())
- return;
- if ((q = ws[i]) != null && q.base - q.top < 0) {
- p.signalWork(q);
- if ((u = (int)(p.ctl >>> 32)) >= 0 ||
- (u >> UAC_SHIFT) >= 0)
- break;
- }
- }
- }
- if (p.tryCompensate()) {
+ while (!blocker.isReleasable()) {
+ if (p.tryCompensate(p.ctl)) {
try {
do {} while (!blocker.isReleasable() &&
!blocker.block());
@@ -3266,6 +3200,7 @@ public class ForkJoinPool extends AbstractExecutorService {
private static final long STEALCOUNT;
private static final long PLOCK;
private static final long INDEXSEED;
+ private static final long QBASE;
private static final long QLOCK;
static {
@@ -3285,6 +3220,8 @@ public class ForkJoinPool extends AbstractExecutorService {
PARKBLOCKER = U.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
Class<?> wk = WorkQueue.class;
+ QBASE = U.objectFieldOffset
+ (wk.getDeclaredField("base"));
QLOCK = U.objectFieldOffset
(wk.getDeclaredField("qlock"));
Class<?> ak = ForkJoinTask[].class;
@@ -3298,45 +3235,51 @@ public class ForkJoinPool extends AbstractExecutorService {
}
submitters = new ThreadLocal<Submitter>();
- ForkJoinWorkerThreadFactory fac = defaultForkJoinWorkerThreadFactory =
+ defaultForkJoinWorkerThreadFactory =
new DefaultForkJoinWorkerThreadFactory();
modifyThreadPermission = new RuntimePermission("modifyThread");
- /*
- * Establish common pool parameters. For extra caution,
- * computations to set up common pool state are here; the
- * constructor just assigns these values to fields.
- */
+ common = java.security.AccessController.doPrivileged
+ (new java.security.PrivilegedAction<ForkJoinPool>() {
+ public ForkJoinPool run() { return makeCommonPool(); }});
+ int par = common.parallelism; // report 1 even if threads disabled
+ commonParallelism = par > 0 ? par : 1;
+ }
- int par = 0;
- Thread.UncaughtExceptionHandler handler = null;
- try { // TBD: limit or report ignored exceptions?
+ /**
+ * Creates and returns the common pool, respecting user settings
+ * specified via system properties.
+ */
+ private static ForkJoinPool makeCommonPool() {
+ int parallelism = -1;
+ ForkJoinWorkerThreadFactory factory
+ = defaultForkJoinWorkerThreadFactory;
+ UncaughtExceptionHandler handler = null;
+ try { // ignore exceptions in accessing/parsing properties
String pp = System.getProperty
("java.util.concurrent.ForkJoinPool.common.parallelism");
- String hp = System.getProperty
- ("java.util.concurrent.ForkJoinPool.common.exceptionHandler");
String fp = System.getProperty
("java.util.concurrent.ForkJoinPool.common.threadFactory");
+ String hp = System.getProperty
+ ("java.util.concurrent.ForkJoinPool.common.exceptionHandler");
+ if (pp != null)
+ parallelism = Integer.parseInt(pp);
if (fp != null)
- fac = ((ForkJoinWorkerThreadFactory)ClassLoader.
- getSystemClassLoader().loadClass(fp).newInstance());
+ factory = ((ForkJoinWorkerThreadFactory)ClassLoader.
+ getSystemClassLoader().loadClass(fp).newInstance());
if (hp != null)
- handler = ((Thread.UncaughtExceptionHandler)ClassLoader.
+ handler = ((UncaughtExceptionHandler)ClassLoader.
getSystemClassLoader().loadClass(hp).newInstance());
- if (pp != null)
- par = Integer.parseInt(pp);
} catch (Exception ignore) {
}
- if (par <= 0)
- par = Runtime.getRuntime().availableProcessors();
- if (par > MAX_CAP)
- par = MAX_CAP;
- commonPoolParallelism = par;
- long np = (long)(-par); // precompute initial ctl value
- long ct = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
-
- commonPool = new ForkJoinPool(par, ct, fac, handler);
+ if (parallelism < 0 && // default 1 less than #cores
+ (parallelism = Runtime.getRuntime().availableProcessors() - 1) < 0)
+ parallelism = 0;
+ if (parallelism > MAX_CAP)
+ parallelism = MAX_CAP;
+ return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE,
+ "ForkJoinPool.commonPool-worker-");
}
}
diff --git a/luni/src/main/java/java/util/concurrent/ForkJoinTask.java b/luni/src/main/java/java/util/concurrent/ForkJoinTask.java
index 818788e..c6bc6de 100644
--- a/luni/src/main/java/java/util/concurrent/ForkJoinTask.java
+++ b/luni/src/main/java/java/util/concurrent/ForkJoinTask.java
@@ -32,8 +32,8 @@ import java.lang.reflect.Constructor;
*
* <p>A "main" {@code ForkJoinTask} begins execution when it is
* explicitly submitted to a {@link ForkJoinPool}, or, if not already
- * engaged in a ForkJoin computation, commenced in the {@link
- * ForkJoinPool#commonPool()} via {@link #fork}, {@link #invoke}, or
+ * engaged in a ForkJoin computation, commenced in the {@code
+ * ForkJoinPool.commonPool()} via {@link #fork}, {@link #invoke}, or
* related methods. Once started, it will usually in turn start other
* subtasks. As indicated by the name of this class, many programs
* using {@code ForkJoinTask} employ only methods {@link #fork} and
@@ -74,10 +74,9 @@ import java.lang.reflect.Constructor;
* but doing do requires three further considerations: (1) Completion
* of few if any <em>other</em> tasks should be dependent on a task
* that blocks on external synchronization or I/O. Event-style async
- * tasks that are never joined (for example, those subclassing {@link
- * CountedCompleter}) often fall into this category. (2) To minimize
- * resource impact, tasks should be small; ideally performing only the
- * (possibly) blocking action. (3) Unless the {@link
+ * tasks that are never joined often fall into this category.
+ * (2) To minimize resource impact, tasks should be small; ideally
+ * performing only the (possibly) blocking action. (3) Unless the {@link
* ForkJoinPool.ManagedBlocker} API is used, or the number of possibly
* blocked tasks is known to be less than the pool's {@link
* ForkJoinPool#getParallelism} level, the pool cannot guarantee that
@@ -120,13 +119,11 @@ import java.lang.reflect.Constructor;
* <p>The ForkJoinTask class is not usually directly subclassed.
* Instead, you subclass one of the abstract classes that support a
* particular style of fork/join processing, typically {@link
- * RecursiveAction} for most computations that do not return results,
- * {@link RecursiveTask} for those that do, and {@link
- * CountedCompleter} for those in which completed actions trigger
- * other actions. Normally, a concrete ForkJoinTask subclass declares
- * fields comprising its parameters, established in a constructor, and
- * then defines a {@code compute} method that somehow uses the control
- * methods supplied by this base class.
+ * RecursiveAction} for most computations that do not return results
+ * and {@link RecursiveTask} for those that do. Normally, a concrete
+ * ForkJoinTask subclass declares fields comprising its parameters,
+ * established in a constructor, and then defines a {@code compute}
+ * method that somehow uses the control methods supplied by this base class.
*
* <p>Method {@link #join} and its variants are appropriate for use
* only when completion dependencies are acyclic; that is, the
@@ -136,11 +133,11 @@ import java.lang.reflect.Constructor;
* supports other methods and techniques (for example the use of
* {@link Phaser}, {@link #helpQuiesce}, and {@link #complete}) that
* may be of use in constructing custom subclasses for problems that
- * are not statically structured as DAGs. To support such usages a
+ * are not statically structured as DAGs. To support such usages, a
* ForkJoinTask may be atomically <em>tagged</em> with a {@code short}
- * value using {@link #setForkJoinTaskTag} or {@link
- * #compareAndSetForkJoinTaskTag} and checked using {@link
- * #getForkJoinTaskTag}. The ForkJoinTask implementation does not use
+ * value using {@code setForkJoinTaskTag} or {@code
+ * compareAndSetForkJoinTaskTag} and checked using {@code
+ * getForkJoinTaskTag}. The ForkJoinTask implementation does not use
* these {@code protected} methods or tags for any purpose, but they
* may be of use in the construction of specialized subclasses. For
* example, parallel graph traversals can use the supplied methods to
@@ -178,7 +175,6 @@ import java.lang.reflect.Constructor;
* execution. Serialization is not relied on during execution itself.
*
* @since 1.7
- * @hide
* @author Doug Lea
*/
public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
@@ -286,25 +282,35 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
*/
private int externalAwaitDone() {
int s;
- ForkJoinPool.externalHelpJoin(this);
- boolean interrupted = false;
- while ((s = status) >= 0) {
- if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
- synchronized (this) {
- if (status >= 0) {
- try {
- wait();
- } catch (InterruptedException ie) {
- interrupted = true;
+ ForkJoinPool cp = ForkJoinPool.common;
+ if ((s = status) >= 0) {
+ if (cp != null) {
+ if (this instanceof CountedCompleter)
+ s = cp.externalHelpComplete((CountedCompleter<?>)this);
+ else if (cp.tryExternalUnpush(this))
+ s = doExec();
+ }
+ if (s >= 0 && (s = status) >= 0) {
+ boolean interrupted = false;
+ do {
+ if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
+ synchronized (this) {
+ if (status >= 0) {
+ try {
+ wait();
+ } catch (InterruptedException ie) {
+ interrupted = true;
+ }
+ }
+ else
+ notifyAll();
}
}
- else
- notifyAll();
- }
+ } while ((s = status) >= 0);
+ if (interrupted)
+ Thread.currentThread().interrupt();
}
}
- if (interrupted)
- Thread.currentThread().interrupt();
return s;
}
@@ -313,9 +319,15 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
*/
private int externalInterruptibleAwaitDone() throws InterruptedException {
int s;
+ ForkJoinPool cp = ForkJoinPool.common;
if (Thread.interrupted())
throw new InterruptedException();
- ForkJoinPool.externalHelpJoin(this);
+ if ((s = status) >= 0 && cp != null) {
+ if (this instanceof CountedCompleter)
+ cp.externalHelpComplete((CountedCompleter<?>)this);
+ else if (cp.tryExternalUnpush(this))
+ doExec();
+ }
while ((s = status) >= 0) {
if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
synchronized (this) {
@@ -329,7 +341,6 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
return s;
}
-
/**
* Implementation for join, get, quietlyJoin. Directly handles
* only cases of already-completed, external wait, and
@@ -601,14 +612,9 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
/**
* A version of "sneaky throw" to relay exceptions
*/
- static void rethrow(final Throwable ex) {
- if (ex != null) {
- if (ex instanceof Error)
- throw (Error)ex;
- if (ex instanceof RuntimeException)
- throw (RuntimeException)ex;
- throw uncheckedThrowable(ex, RuntimeException.class);
- }
+ static void rethrow(Throwable ex) {
+ if (ex != null)
+ ForkJoinTask.<RuntimeException>uncheckedThrow(ex);
}
/**
@@ -617,8 +623,8 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
* unchecked exceptions
*/
@SuppressWarnings("unchecked") static <T extends Throwable>
- T uncheckedThrowable(final Throwable t, final Class<T> c) {
- return (T)t; // rely on vacuous cast
+ void uncheckedThrow(Throwable t) throws T {
+ throw (T)t; // rely on vacuous cast
}
/**
@@ -635,8 +641,8 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
/**
* Arranges to asynchronously execute this task in the pool the
- * current task is running in, if applicable, or using the {@link
- * ForkJoinPool#commonPool()} if not {@link #inForkJoinPool}. While
+ * current task is running in, if applicable, or using the {@code
+ * ForkJoinPool.commonPool()} if not {@link #inForkJoinPool}. While
* it is not necessarily enforced, it is a usage error to fork a
* task more than once unless it has completed and been
* reinitialized. Subsequent modifications to the state of this
@@ -653,7 +659,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
- ForkJoinPool.commonPool.externalPush(this);
+ ForkJoinPool.common.externalPush(this);
return this;
}
@@ -774,8 +780,6 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
* @param tasks the collection of tasks
* @return the tasks argument, to simplify usage
* @throws NullPointerException if tasks or any element are null
-
- * @hide
*/
public static <T extends ForkJoinTask<?>> Collection<T> invokeAll(Collection<T> tasks) {
if (!(tasks instanceof RandomAccess) || !(tasks instanceof List<?>)) {
@@ -831,7 +835,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
* <p>This method is designed to be invoked by <em>other</em>
* tasks. To terminate the current task, you can just return or
* throw an unchecked exception from its computation method, or
- * invoke {@link #completeExceptionally}.
+ * invoke {@link #completeExceptionally(Throwable)}.
*
* @param mayInterruptIfRunning this value has no effect in the
* default implementation because interrupts are not used to
@@ -984,6 +988,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
// Messy in part because we measure in nanosecs, but wait in millisecs
int s; long ms;
long ns = unit.toNanos(timeout);
+ ForkJoinPool cp;
if ((s = status) >= 0 && ns > 0L) {
long deadline = System.nanoTime() + ns;
ForkJoinPool p = null;
@@ -995,8 +1000,12 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
w = wt.workQueue;
p.helpJoinOnce(w, this); // no retries on failure
}
- else
- ForkJoinPool.externalHelpJoin(this);
+ else if ((cp = ForkJoinPool.common) != null) {
+ if (this instanceof CountedCompleter)
+ cp.externalHelpComplete((CountedCompleter<?>)this);
+ else if (cp.tryExternalUnpush(this))
+ doExec();
+ }
boolean canBlock = false;
boolean interrupted = false;
try {
@@ -1004,7 +1013,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
if (w != null && w.qlock < 0)
cancelIgnoringExceptions(this);
else if (!canBlock) {
- if (p == null || p.tryCompensate())
+ if (p == null || p.tryCompensate(p.ctl))
canBlock = true;
}
else {
@@ -1080,7 +1089,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
wt.pool.helpQuiescePool(wt.workQueue);
}
else
- ForkJoinPool.externalHelpQuiescePool();
+ ForkJoinPool.quiesceCommonPool();
}
/**
@@ -1145,7 +1154,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
Thread t;
return (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
((ForkJoinWorkerThread)t).workQueue.tryUnpush(this) :
- ForkJoinPool.tryExternalUnpush(this));
+ ForkJoinPool.common.tryExternalUnpush(this));
}
/**
@@ -1316,7 +1325,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
*
* @param e the expected tag value
* @param tag the new tag value
- * @return true if successful; i.e., the current value was
+ * @return {@code true} if successful; i.e., the current value was
* equal to e and is now tag.
* @since 1.8
* @hide
@@ -1370,6 +1379,24 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
}
/**
+ * Adaptor for Runnables in which failure forces worker exception
+ */
+ static final class RunnableExecuteAction extends ForkJoinTask<Void> {
+ final Runnable runnable;
+ RunnableExecuteAction(Runnable runnable) {
+ if (runnable == null) throw new NullPointerException();
+ this.runnable = runnable;
+ }
+ public final Void getRawResult() { return null; }
+ public final void setRawResult(Void v) { }
+ public final boolean exec() { runnable.run(); return true; }
+ void internalPropagateException(Throwable ex) {
+ rethrow(ex); // rethrow outside exec() catches.
+ }
+ private static final long serialVersionUID = 5232453952276885070L;
+ }
+
+ /**
* Adaptor for Callables
*/
static final class AdaptedCallable<T> extends ForkJoinTask<T>
@@ -1480,5 +1507,4 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
throw new Error(e);
}
}
-
}
diff --git a/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java b/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java
index f31763c..ae28700 100644
--- a/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java
+++ b/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java
@@ -14,11 +14,10 @@ package java.util.concurrent;
* scheduling or execution. However, you can override initialization
* and termination methods surrounding the main task processing loop.
* If you do create such a subclass, you will also need to supply a
- * custom {@link ForkJoinPool.ForkJoinWorkerThreadFactory} to use it
- * in a {@code ForkJoinPool}.
+ * custom {@link ForkJoinPool.ForkJoinWorkerThreadFactory} to
+ * {@linkplain ForkJoinPool#ForkJoinPool use it} in a {@code ForkJoinPool}.
*
* @since 1.7
- * @hide
* @author Doug Lea
*/
public class ForkJoinWorkerThread extends Thread {
@@ -61,16 +60,17 @@ public class ForkJoinWorkerThread extends Thread {
}
/**
- * Returns the index number of this thread in its pool. The
- * returned value ranges from zero to the maximum number of
- * threads (minus one) that have ever been created in the pool.
- * This method may be useful for applications that track status or
- * collect results per-worker rather than per-task.
+ * Returns the unique index number of this thread in its pool.
+ * The returned value ranges from zero to the maximum number of
+ * threads (minus one) that may exist in the pool, and does not
+ * change during the lifetime of the thread. This method may be
+ * useful for applications that track status or collect results
+ * per-worker-thread rather than per-task.
*
* @return the index number
*/
public int getPoolIndex() {
- return workQueue.poolIndex;
+ return workQueue.poolIndex >>> 1; // ignore odd/even tag bit
}
/**
diff --git a/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java b/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java
index cff5dbf..a041fb1 100644
--- a/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java
+++ b/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java
@@ -50,7 +50,6 @@ import java.util.concurrent.locks.LockSupport;
* the {@code LinkedTransferQueue} in another thread.
*
* @since 1.7
- * @hide
* @author Doug Lea
* @param <E> the type of elements held in this collection
*/
diff --git a/luni/src/main/java/java/util/concurrent/Phaser.java b/luni/src/main/java/java/util/concurrent/Phaser.java
index a9adbe5..a97d187 100644
--- a/luni/src/main/java/java/util/concurrent/Phaser.java
+++ b/luni/src/main/java/java/util/concurrent/Phaser.java
@@ -227,7 +227,6 @@ import java.util.concurrent.locks.LockSupport;
* of participants.
*
* @since 1.7
- * @hide
* @author Doug Lea
*/
public class Phaser {
diff --git a/luni/src/main/java/java/util/concurrent/RecursiveAction.java b/luni/src/main/java/java/util/concurrent/RecursiveAction.java
index 8d666f6..e3a6340 100644
--- a/luni/src/main/java/java/util/concurrent/RecursiveAction.java
+++ b/luni/src/main/java/java/util/concurrent/RecursiveAction.java
@@ -131,7 +131,6 @@ package java.util.concurrent;
* }}</pre>
*
* @since 1.7
- * @hide
* @author Doug Lea
*/
public abstract class RecursiveAction extends ForkJoinTask<Void> {
diff --git a/luni/src/main/java/java/util/concurrent/RecursiveTask.java b/luni/src/main/java/java/util/concurrent/RecursiveTask.java
index 421c9d3..80baa52 100644
--- a/luni/src/main/java/java/util/concurrent/RecursiveTask.java
+++ b/luni/src/main/java/java/util/concurrent/RecursiveTask.java
@@ -34,7 +34,6 @@ package java.util.concurrent;
* sequentially solve rather than subdividing.
*
* @since 1.7
- * @hide
* @author Doug Lea
*/
public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
diff --git a/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java b/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java
index a52351b..483981d 100644
--- a/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java
+++ b/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java
@@ -690,7 +690,6 @@ public class ScheduledThreadPoolExecutor
* @param value if {@code true}, remove on cancellation, else don't
* @see #getRemoveOnCancelPolicy
* @since 1.7
- * @hide
*/
public void setRemoveOnCancelPolicy(boolean value) {
removeOnCancel = value;
@@ -705,7 +704,6 @@ public class ScheduledThreadPoolExecutor
* from the queue
* @see #setRemoveOnCancelPolicy
* @since 1.7
- * @hide
*/
public boolean getRemoveOnCancelPolicy() {
return removeOnCancel;
diff --git a/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java b/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java
index a559321..5baf75f 100644
--- a/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java
+++ b/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java
@@ -30,7 +30,6 @@ import java.util.Random;
* generation methods.
*
* @since 1.7
- * @hide
* @author Doug Lea
*/
public class ThreadLocalRandom extends Random {
diff --git a/luni/src/main/java/java/util/concurrent/TransferQueue.java b/luni/src/main/java/java/util/concurrent/TransferQueue.java
index 9cd5773..4c2be6f 100644
--- a/luni/src/main/java/java/util/concurrent/TransferQueue.java
+++ b/luni/src/main/java/java/util/concurrent/TransferQueue.java
@@ -33,7 +33,6 @@ package java.util.concurrent;
* and {@code transfer} are effectively synonymous.
*
* @since 1.7
- * @hide
* @author Doug Lea
* @param <E> the type of elements held in this collection
*/
diff --git a/luni/src/main/java/java/util/concurrent/atomic/Fences.java b/luni/src/main/java/java/util/concurrent/atomic/Fences.java
index 7ecf45a..5714ba0 100644
--- a/luni/src/main/java/java/util/concurrent/atomic/Fences.java
+++ b/luni/src/main/java/java/util/concurrent/atomic/Fences.java
@@ -453,7 +453,6 @@ package java.util.concurrent.atomic;
*
* </dl>
*
- * @since 1.7
* @hide
* @author Doug Lea
*/
diff --git a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java
index 4c5e280..37aa9d0 100644
--- a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java
+++ b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java
@@ -1255,7 +1255,6 @@ public abstract class AbstractQueuedLongSynchronizer
* current thread, and {@code false} if the current thread
* is at the head of the queue or the queue is empty
* @since 1.7
- * @hide
*/
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
diff --git a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java
index 0350060..e711da5 100644
--- a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java
+++ b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java
@@ -1485,7 +1485,6 @@ public abstract class AbstractQueuedSynchronizer
* current thread, and {@code false} if the current thread
* is at the head of the queue or the queue is empty
* @since 1.7
- * @hide
*/
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
diff --git a/luni/src/main/java/java/util/jar/Attributes.java b/luni/src/main/java/java/util/jar/Attributes.java
index 7e32897..483621b 100644
--- a/luni/src/main/java/java/util/jar/Attributes.java
+++ b/luni/src/main/java/java/util/jar/Attributes.java
@@ -288,7 +288,7 @@ public class Attributes implements Cloneable, Map<Object, Object> {
* @param value
* the value to store in this {@code Attributes}.
* @return the value being stored.
- * @exception ClassCastException
+ * @throws ClassCastException
* when key is not an {@code Attributes.Name} or value is not
* a {@code String}.
*/
@@ -307,9 +307,14 @@ public class Attributes implements Cloneable, Map<Object, Object> {
* Attributes}).
*/
public void putAll(Map<?, ?> attrib) {
- if (attrib == null || !(attrib instanceof Attributes)) {
+ if (attrib == null) {
+ throw new NullPointerException("attrib == null");
+ }
+
+ if (!(attrib instanceof Attributes)) {
throw new ClassCastException(attrib.getClass().getName() + " not an Attributes");
}
+
this.map.putAll(attrib);
}
diff --git a/luni/src/main/java/java/util/jar/JarEntry.java b/luni/src/main/java/java/util/jar/JarEntry.java
index 381dd52..bceef63 100644
--- a/luni/src/main/java/java/util/jar/JarEntry.java
+++ b/luni/src/main/java/java/util/jar/JarEntry.java
@@ -20,14 +20,14 @@ package java.util.jar;
import java.io.IOException;
import java.security.CodeSigner;
import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
import java.util.zip.ZipEntry;
-import javax.security.auth.x500.X500Principal;
/**
* Represents a single file in a JAR archive together with the manifest
@@ -39,7 +39,7 @@ import javax.security.auth.x500.X500Principal;
public class JarEntry extends ZipEntry {
private Attributes attributes;
- JarFile parentJar;
+ final JarFile parentJar;
CodeSigner signers[];
@@ -56,6 +56,7 @@ public class JarEntry extends ZipEntry {
*/
public JarEntry(String name) {
super(name);
+ parentJar = null;
}
/**
@@ -65,15 +66,35 @@ public class JarEntry extends ZipEntry {
* The ZipEntry to obtain values from.
*/
public JarEntry(ZipEntry entry) {
+ this(entry, null);
+ }
+
+ JarEntry(ZipEntry entry, JarFile parentJar) {
super(entry);
+ this.parentJar = parentJar;
+ }
+
+ /**
+ * Create a new {@code JarEntry} using the values obtained from the
+ * argument.
+ *
+ * @param je
+ * The {@code JarEntry} to obtain values from.
+ */
+ public JarEntry(JarEntry je) {
+ super(je);
+ parentJar = je.parentJar;
+ attributes = je.attributes;
+ signers = je.signers;
}
+
/**
* Returns the {@code Attributes} object associated with this entry or
* {@code null} if none exists.
*
* @return the {@code Attributes} for this entry.
- * @exception IOException
+ * @throws IOException
* If an error occurs obtaining the {@code Attributes}.
* @see Attributes
*/
@@ -93,8 +114,12 @@ public class JarEntry extends ZipEntry {
* entry or {@code null} if none exists. Make sure that the everything is
* read from the input stream before calling this method, or else the method
* returns {@code null}.
+ * <p>
+ * This method returns all the signers' unverified chains concatenated
+ * together in one array. To know which certificates were tied to the
+ * private keys that made the signatures on this entry, see
+ * {@link #getCodeSigners()} instead.
*
- * @return the certificate for this entry.
* @see java.security.cert.Certificate
*/
public Certificate[] getCertificates() {
@@ -105,7 +130,27 @@ public class JarEntry extends ZipEntry {
if (jarVerifier == null) {
return null;
}
- return jarVerifier.getCertificates(getName());
+
+ Certificate[][] certChains = jarVerifier.getCertificateChains(getName());
+ if (certChains == null) {
+ return null;
+ }
+
+ // Measure number of certs.
+ int count = 0;
+ for (Certificate[] chain : certChains) {
+ count += chain.length;
+ }
+
+ // Create new array and copy all the certs into it.
+ Certificate[] certs = new Certificate[count];
+ int i = 0;
+ for (Certificate[] chain : certChains) {
+ System.arraycopy(chain, 0, certs, i, chain.length);
+ i += chain.length;
+ }
+
+ return certs;
}
void setAttributes(Attributes attrib) {
@@ -113,86 +158,64 @@ public class JarEntry extends ZipEntry {
}
/**
- * Create a new {@code JarEntry} using the values obtained from the
- * argument.
- *
- * @param je
- * The {@code JarEntry} to obtain values from.
- */
- public JarEntry(JarEntry je) {
- super(je);
- parentJar = je.parentJar;
- attributes = je.attributes;
- signers = je.signers;
- }
-
- /**
* Returns the code signers for the digital signatures associated with the
* JAR file. If there is no such code signer, it returns {@code null}. Make
* sure that the everything is read from the input stream before calling
* this method, or else the method returns {@code null}.
+ * <p>
+ * Only the digital signature on the entry is cryptographically verified.
+ * None of the certificates in the the {@link CertPath} returned from
+ * {@link CodeSigner#getSignerCertPath()} are verified and must be verified
+ * by the caller if needed. See {@link CertPathValidator} for more
+ * information.
*
- * @return the code signers for the JAR entry.
+ * @return an array of CodeSigner for this JAR entry.
* @see CodeSigner
*/
public CodeSigner[] getCodeSigners() {
+ if (parentJar == null) {
+ return null;
+ }
+
+ JarVerifier jarVerifier = parentJar.verifier;
+ if (jarVerifier == null) {
+ return null;
+ }
+
if (signers == null) {
- signers = getCodeSigners(getCertificates());
+ signers = getCodeSigners(jarVerifier.getCertificateChains(getName()));
}
if (signers == null) {
return null;
}
- CodeSigner[] tmp = new CodeSigner[signers.length];
- System.arraycopy(signers, 0, tmp, 0, tmp.length);
- return tmp;
+ return signers.clone();
}
- private CodeSigner[] getCodeSigners(Certificate[] certs) {
- if (certs == null) {
+ private CodeSigner[] getCodeSigners(Certificate[][] certChains) {
+ if (certChains == null) {
return null;
}
- X500Principal prevIssuer = null;
- ArrayList<Certificate> list = new ArrayList<Certificate>(certs.length);
- ArrayList<CodeSigner> asigners = new ArrayList<CodeSigner>();
+ ArrayList<CodeSigner> asigners = new ArrayList<CodeSigner>(certChains.length);
- for (Certificate element : certs) {
- if (!(element instanceof X509Certificate)) {
- // Only X509Certificate-s are taken into account - see API spec.
- continue;
- }
- X509Certificate x509 = (X509Certificate) element;
- if (prevIssuer != null) {
- X500Principal subj = x509.getSubjectX500Principal();
- if (!prevIssuer.equals(subj)) {
- // Ok, this ends the previous chain,
- // so transform this one into CertPath ...
- addCodeSigner(asigners, list);
- // ... and start a new one
- list.clear();
- }// else { it's still the same chain }
-
- }
- prevIssuer = x509.getIssuerX500Principal();
- list.add(x509);
- }
- if (!list.isEmpty()) {
- addCodeSigner(asigners, list);
- }
- if (asigners.isEmpty()) {
- // 'signers' is 'null' already
- return null;
+ for (Certificate[] chain : certChains) {
+ addCodeSigner(asigners, chain);
}
CodeSigner[] tmp = new CodeSigner[asigners.size()];
asigners.toArray(tmp);
return tmp;
-
}
- private void addCodeSigner(ArrayList<CodeSigner> asigners,
- List<Certificate> list) {
+ private void addCodeSigner(ArrayList<CodeSigner> asigners, Certificate[] certs) {
+ for (Certificate cert : certs) {
+ // Only X509Certificate instances are counted. See API spec.
+ if (!(cert instanceof X509Certificate)) {
+ return;
+ }
+ }
+
CertPath certPath = null;
if (!isFactoryChecked) {
try {
@@ -207,7 +230,7 @@ public class JarEntry extends ZipEntry {
return;
}
try {
- certPath = factory.generateCertPath(list);
+ certPath = factory.generateCertPath(Arrays.asList(certs));
} catch (CertificateException ex) {
// do nothing
}
diff --git a/luni/src/main/java/java/util/jar/JarFile.java b/luni/src/main/java/java/util/jar/JarFile.java
index 5293a89..6b147f6 100644
--- a/luni/src/main/java/java/util/jar/JarFile.java
+++ b/luni/src/main/java/java/util/jar/JarFile.java
@@ -23,7 +23,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import libcore.io.Streams;
@@ -48,28 +50,24 @@ public class JarFile extends ZipFile {
// The manifest after it has been read from the JAR.
private Manifest manifest;
- // The entry for the MANIFEST.MF file before it is read.
- private ZipEntry manifestEntry;
+ // The entry for the MANIFEST.MF file before the first call to getManifest().
+ private byte[] manifestBytes;
JarVerifier verifier;
private boolean closed = false;
static final class JarFileInputStream extends FilterInputStream {
- private long count;
-
- private ZipEntry zipEntry;
-
- private JarVerifier.VerifierEntry entry;
+ private final JarVerifier.VerifierEntry entry;
+ private long count;
private boolean done = false;
- JarFileInputStream(InputStream is, ZipEntry ze,
- JarVerifier.VerifierEntry e) {
+ JarFileInputStream(InputStream is, long size, JarVerifier.VerifierEntry e) {
super(is);
- zipEntry = ze;
- count = zipEntry.getSize();
entry = e;
+
+ count = size;
}
@Override
@@ -140,6 +138,24 @@ public class JarFile extends ZipFile {
}
}
+ static final class JarFileEnumerator implements Enumeration<JarEntry> {
+ final Enumeration<? extends ZipEntry> ze;
+ final JarFile jf;
+
+ JarFileEnumerator(Enumeration<? extends ZipEntry> zenum, JarFile jf) {
+ ze = zenum;
+ this.jf = jf;
+ }
+
+ public boolean hasMoreElements() {
+ return ze.hasMoreElements();
+ }
+
+ public JarEntry nextElement() {
+ return new JarEntry(ze.nextElement(), jf /* parentJar */);
+ }
+ }
+
/**
* Create a new {@code JarFile} using the contents of the specified file.
*
@@ -163,11 +179,7 @@ public class JarFile extends ZipFile {
* If the file cannot be read.
*/
public JarFile(File file, boolean verify) throws IOException {
- super(file);
- if (verify) {
- verifier = new JarVerifier(file.getPath());
- }
- readMetaEntries();
+ this(file, verify, ZipFile.OPEN_READ);
}
/**
@@ -184,21 +196,30 @@ public class JarFile extends ZipFile {
* If the file cannot be read.
*/
public JarFile(File file, boolean verify, int mode) throws IOException {
- this(file, verify, mode, false);
- }
-
- /**
- * See previous constructor for other parameter definitions.
- * @param chainCheck
- * whether or not to check certificate chain signatures
- * @hide
- */
- public JarFile(File file, boolean verify, int mode, boolean chainCheck) throws IOException {
super(file, mode);
- if (verify) {
- verifier = new JarVerifier(file.getPath(), chainCheck);
+
+ // Step 1: Scan the central directory for meta entries (MANIFEST.mf
+ // & possibly the signature files) and read them fully.
+ HashMap<String, byte[]> metaEntries = readMetaEntries(this, verify);
+
+ // Step 2: Construct a verifier with the information we have.
+ // Verification is possible *only* if the JAR file contains a manifest
+ // *AND* it contains signing related information (signature block
+ // files and the signature files).
+ //
+ // TODO: Is this really the behaviour we want if verify == true ?
+ // We silently skip verification for files that have no manifest or
+ // no signatures.
+ if (verify && metaEntries.containsKey(MANIFEST_NAME) &&
+ metaEntries.size() > 1) {
+ // We create the manifest straight away, so that we can create
+ // the jar verifier as well.
+ manifest = new Manifest(metaEntries.get(MANIFEST_NAME), true);
+ verifier = new JarVerifier(getName(), manifest, metaEntries);
+ } else {
+ verifier = null;
+ manifestBytes = metaEntries.get(MANIFEST_NAME);
}
- readMetaEntries();
}
/**
@@ -226,21 +247,7 @@ public class JarFile extends ZipFile {
* If file cannot be opened or read.
*/
public JarFile(String filename, boolean verify) throws IOException {
- this(filename, verify, false);
- }
-
- /**
- * See previous constructor for other parameter definitions.
- * @param chainCheck
- * whether or not to check certificate chain signatures
- * @hide
- */
- public JarFile(String filename, boolean verify, boolean chainCheck) throws IOException {
- super(filename);
- if (verify) {
- verifier = new JarVerifier(filename, chainCheck);
- }
- readMetaEntries();
+ this(new File(filename), verify, ZipFile.OPEN_READ);
}
/**
@@ -253,26 +260,6 @@ public class JarFile extends ZipFile {
*/
@Override
public Enumeration<JarEntry> entries() {
- class JarFileEnumerator implements Enumeration<JarEntry> {
- Enumeration<? extends ZipEntry> ze;
-
- JarFile jf;
-
- JarFileEnumerator(Enumeration<? extends ZipEntry> zenum, JarFile jf) {
- ze = zenum;
- this.jf = jf;
- }
-
- public boolean hasMoreElements() {
- return ze.hasMoreElements();
- }
-
- public JarEntry nextElement() {
- JarEntry je = new JarEntry(ze.nextElement());
- je.parentJar = jf;
- return je;
- }
- }
return new JarFileEnumerator(super.entries(), this);
}
@@ -303,73 +290,70 @@ public class JarFile extends ZipFile {
if (closed) {
throw new IllegalStateException("JarFile has been closed");
}
+
if (manifest != null) {
return manifest;
}
- try {
- InputStream is = super.getInputStream(manifestEntry);
- if (verifier != null) {
- verifier.addMetaEntry(manifestEntry.getName(), Streams.readFully(is));
- is = super.getInputStream(manifestEntry);
- }
- try {
- manifest = new Manifest(is, verifier != null);
- } finally {
- is.close();
- }
- manifestEntry = null; // Can discard the entry now.
- } catch (NullPointerException e) {
- manifestEntry = null;
+
+ // If manifest == null && manifestBytes == null, there's no manifest.
+ if (manifestBytes == null) {
+ return null;
}
+
+ // We hit this code path only if the verification isn't necessary. If
+ // we did decide to verify this file, we'd have created the Manifest and
+ // the associated Verifier in the constructor itself.
+ manifest = new Manifest(manifestBytes, false);
+ manifestBytes = null;
+
return manifest;
}
/**
- * Called by the JarFile constructors, this method reads the contents of the
+ * Called by the JarFile constructors, Reads the contents of the
* file's META-INF/ directory and picks out the MANIFEST.MF file and
- * verifier signature files if they exist. Any signature files found are
- * registered with the verifier.
+ * verifier signature files if they exist.
*
* @throws IOException
* if there is a problem reading the jar file entries.
+ * @return a map of entry names to their {@code byte[]} content.
*/
- private void readMetaEntries() throws IOException {
+ static HashMap<String, byte[]> readMetaEntries(ZipFile zipFile,
+ boolean verificationRequired) throws IOException {
// Get all meta directory entries
- ZipEntry[] metaEntries = getMetaEntriesImpl();
- if (metaEntries == null) {
- verifier = null;
- return;
- }
+ List<ZipEntry> metaEntries = getMetaEntries(zipFile);
- boolean signed = false;
+ HashMap<String, byte[]> metaEntriesMap = new HashMap<String, byte[]>();
for (ZipEntry entry : metaEntries) {
String entryName = entry.getName();
// Is this the entry for META-INF/MANIFEST.MF ?
- if (manifestEntry == null && entryName.equalsIgnoreCase(MANIFEST_NAME)) {
- manifestEntry = entry;
+ //
+ // TODO: Why do we need the containsKey check ? Shouldn't we discard
+ // files that contain duplicate entries like this as invalid ?.
+ if (entryName.equalsIgnoreCase(MANIFEST_NAME) &&
+ !metaEntriesMap.containsKey(MANIFEST_NAME)) {
+
+ metaEntriesMap.put(MANIFEST_NAME, Streams.readFully(
+ zipFile.getInputStream(entry)));
+
// If there is no verifier then we don't need to look any further.
- if (verifier == null) {
+ if (!verificationRequired) {
break;
}
- } else {
+ } else if (verificationRequired) {
// Is this an entry that the verifier needs?
- if (verifier != null
- && (endsWithIgnoreCase(entryName, ".SF")
- || endsWithIgnoreCase(entryName, ".DSA")
- || endsWithIgnoreCase(entryName, ".RSA")
- || endsWithIgnoreCase(entryName, ".EC"))) {
- signed = true;
- InputStream is = super.getInputStream(entry);
- verifier.addMetaEntry(entryName, Streams.readFully(is));
+ if (endsWithIgnoreCase(entryName, ".SF")
+ || endsWithIgnoreCase(entryName, ".DSA")
+ || endsWithIgnoreCase(entryName, ".RSA")
+ || endsWithIgnoreCase(entryName, ".EC")) {
+ InputStream is = zipFile.getInputStream(entry);
+ metaEntriesMap.put(entryName.toUpperCase(Locale.US), Streams.readFully(is));
}
}
}
- // If there were no signature files, then no verifier work to do.
- if (!signed) {
- verifier = null;
- }
+ return metaEntriesMap;
}
private static boolean endsWithIgnoreCase(String s, String suffix) {
@@ -388,24 +372,21 @@ public class JarFile extends ZipFile {
*/
@Override
public InputStream getInputStream(ZipEntry ze) throws IOException {
- if (manifestEntry != null) {
+ if (manifestBytes != null) {
getManifest();
}
+
if (verifier != null) {
- verifier.setManifest(getManifest());
- if (manifest != null) {
- verifier.mainAttributesEnd = manifest.getMainAttributesEnd();
- }
if (verifier.readCertificates()) {
verifier.removeMetaEntries();
- if (manifest != null) {
- manifest.removeChunks();
- }
+ manifest.removeChunks();
+
if (!verifier.isSignedJar()) {
verifier = null;
}
}
}
+
InputStream in = super.getInputStream(ze);
if (in == null) {
return null;
@@ -417,7 +398,7 @@ public class JarFile extends ZipFile {
if (entry == null) {
return in;
}
- return new JarFileInputStream(in, ze, entry);
+ return new JarFileInputStream(in, ze.getSize(), entry);
}
/**
@@ -434,20 +415,17 @@ public class JarFile extends ZipFile {
if (ze == null) {
return ze;
}
- JarEntry je = new JarEntry(ze);
- je.parentJar = this;
- return je;
+ return new JarEntry(ze, this /* parentJar */);
}
/**
* Returns all the ZipEntry's that relate to files in the
* JAR's META-INF directory.
- *
- * @return the list of ZipEntry's or {@code null} if there are none.
*/
- private ZipEntry[] getMetaEntriesImpl() {
+ private static List<ZipEntry> getMetaEntries(ZipFile zipFile) {
List<ZipEntry> list = new ArrayList<ZipEntry>(8);
- Enumeration<? extends ZipEntry> allEntries = entries();
+
+ Enumeration<? extends ZipEntry> allEntries = zipFile.entries();
while (allEntries.hasMoreElements()) {
ZipEntry ze = allEntries.nextElement();
if (ze.getName().startsWith(META_DIR)
@@ -455,12 +433,8 @@ public class JarFile extends ZipFile {
list.add(ze);
}
}
- if (list.size() == 0) {
- return null;
- }
- ZipEntry[] result = new ZipEntry[list.size()];
- list.toArray(result);
- return result;
+
+ return list;
}
/**
diff --git a/luni/src/main/java/java/util/jar/JarInputStream.java b/luni/src/main/java/java/util/jar/JarInputStream.java
index 5e08b5d..585c135 100644
--- a/luni/src/main/java/java/util/jar/JarInputStream.java
+++ b/luni/src/main/java/java/util/jar/JarInputStream.java
@@ -21,9 +21,11 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.HashMap;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
+import libcore.io.Streams;
/**
* The input stream from which the JAR file to be read may be fetched. It is
@@ -31,15 +33,20 @@ import java.util.zip.ZipInputStream;
*
* @see ZipInputStream
*/
+// TODO: The semantics provided by this class are really weird. The jar file
+// spec does not impose any ordering constraints on the entries of a jar file.
+// In particular, the Manifest and META-INF directory *need not appear first*. This
+// class will silently skip certificate checks for jar files where the manifest
+// isn't the first entry. To do this correctly, we need O(input_stream_length) memory.
public class JarInputStream extends ZipInputStream {
private Manifest manifest;
- private boolean eos = false;
+ private boolean verified = false;
- private JarEntry mEntry;
+ private JarEntry currentJarEntry;
- private JarEntry jarEntry;
+ private JarEntry pendingJarEntry;
private boolean isMeta;
@@ -60,38 +67,44 @@ public class JarInputStream extends ZipInputStream {
*/
public JarInputStream(InputStream stream, boolean verify) throws IOException {
super(stream);
- if (verify) {
- verifier = new JarVerifier("JarInputStream");
- }
- if ((mEntry = getNextJarEntry()) == null) {
+
+ verifier = null;
+ pendingJarEntry = null;
+ currentJarEntry = null;
+
+ if (getNextJarEntry() == null) {
return;
}
- if (mEntry.getName().equalsIgnoreCase(JarFile.META_DIR)) {
- mEntry = null; // modifies behavior of getNextJarEntry()
+
+ if (currentJarEntry.getName().equalsIgnoreCase(JarFile.META_DIR)) {
+ // Fetch the next entry, in the hope that it's the manifest file.
closeEntry();
- mEntry = getNextJarEntry();
+ getNextJarEntry();
}
- if (mEntry.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)) {
- mEntry = null;
- manifest = new Manifest(this, verify);
+
+ if (currentJarEntry.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)) {
+ final byte[] manifestBytes = Streams.readFullyNoClose(this);
+ manifest = new Manifest(manifestBytes, verify);
closeEntry();
+
if (verify) {
- verifier.setManifest(manifest);
- if (manifest != null) {
- verifier.mainAttributesEnd = manifest.getMainAttributesEnd();
- }
+ HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
+ metaEntries.put(JarFile.MANIFEST_NAME, manifestBytes);
+ verifier = new JarVerifier("JarInputStream", manifest, metaEntries);
}
-
- } else {
- Attributes temp = new Attributes(3);
- temp.map.put("hidden", null);
- mEntry.setAttributes(temp);
- /*
- * if not from the first entry, we will not get enough
- * information,so no verify will be taken out.
- */
- verifier = null;
}
+
+ // There was no manifest available, so we should return the current
+ // entry the next time getNextEntry is called.
+ pendingJarEntry = currentJarEntry;
+ currentJarEntry = null;
+
+ // If the manifest isn't the first entry, we will not have enough
+ // information to perform verification on entries that precede it.
+ //
+ // TODO: Should we throw if verify == true in this case ?
+ // TODO: We need all meta entries to be placed before the manifest
+ // as well.
}
/**
@@ -138,32 +151,39 @@ public class JarInputStream extends ZipInputStream {
*/
@Override
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
- if (mEntry != null) {
+ if (currentJarEntry == null) {
return -1;
}
+
int r = super.read(buffer, byteOffset, byteCount);
- if (verStream != null && !eos) {
+ // verifier can be null if we've been asked not to verify or if
+ // the manifest wasn't found.
+ //
+ // verStream will be null if we're reading the manifest or if we have
+ // no signatures or if the digest for this entry isn't present in the
+ // manifest.
+ if (verifier != null && verStream != null && !verified) {
if (r == -1) {
- eos = true;
- if (verifier != null) {
- if (isMeta) {
- verifier.addMetaEntry(jarEntry.getName(),
- ((ByteArrayOutputStream) verStream)
- .toByteArray());
- try {
- verifier.readCertificates();
- } catch (SecurityException e) {
- verifier = null;
- throw e;
- }
- } else {
- ((JarVerifier.VerifierEntry) verStream).verify();
+ // We've hit the end of this stream for the first time, so attempt
+ // a verification.
+ verified = true;
+ if (isMeta) {
+ verifier.addMetaEntry(currentJarEntry.getName(),
+ ((ByteArrayOutputStream) verStream).toByteArray());
+ try {
+ verifier.readCertificates();
+ } catch (SecurityException e) {
+ verifier = null;
+ throw e;
}
+ } else {
+ ((JarVerifier.VerifierEntry) verStream).verify();
}
} else {
verStream.write(buffer, byteOffset, r);
}
}
+
return r;
}
@@ -177,26 +197,47 @@ public class JarInputStream extends ZipInputStream {
*/
@Override
public ZipEntry getNextEntry() throws IOException {
- if (mEntry != null) {
- jarEntry = mEntry;
- mEntry = null;
- jarEntry.setAttributes(null);
- } else {
- jarEntry = (JarEntry) super.getNextEntry();
- if (jarEntry == null) {
- return null;
- }
- if (verifier != null) {
- isMeta = jarEntry.getName().toUpperCase(Locale.US).startsWith(JarFile.META_DIR);
- if (isMeta) {
- verStream = new ByteArrayOutputStream();
- } else {
- verStream = verifier.initEntry(jarEntry.getName());
- }
+ // NOTE: This function must update the value of currentJarEntry
+ // as a side effect.
+
+ if (pendingJarEntry != null) {
+ JarEntry pending = pendingJarEntry;
+ pendingJarEntry = null;
+ currentJarEntry = pending;
+ return pending;
+ }
+
+ currentJarEntry = (JarEntry) super.getNextEntry();
+ if (currentJarEntry == null) {
+ return null;
+ }
+
+ if (verifier != null) {
+ isMeta = currentJarEntry.getName().toUpperCase(Locale.US).startsWith(JarFile.META_DIR);
+ if (isMeta) {
+ final int entrySize = (int) currentJarEntry.getSize();
+ verStream = new ByteArrayOutputStream(entrySize > 0 ? entrySize : 8192);
+ } else {
+ verStream = verifier.initEntry(currentJarEntry.getName());
}
}
- eos = false;
- return jarEntry;
+
+ verified = false;
+ return currentJarEntry;
+ }
+
+ @Override
+ public void closeEntry() throws IOException {
+ // NOTE: This was the old behavior. A call to closeEntry() before the
+ // first call to getNextEntry should be a no-op. If we don't return early
+ // here, the super class will close pendingJarEntry for us and reads will
+ // fail.
+ if (pendingJarEntry != null) {
+ return;
+ }
+
+ super.closeEntry();
+ currentJarEntry = null;
}
@Override
diff --git a/luni/src/main/java/java/util/jar/JarVerifier.java b/luni/src/main/java/java/util/jar/JarVerifier.java
index 8185c6d..467e298 100644
--- a/luni/src/main/java/java/util/jar/JarVerifier.java
+++ b/luni/src/main/java/java/util/jar/JarVerifier.java
@@ -17,6 +17,7 @@
package java.util.jar;
+import org.apache.harmony.security.utils.JarUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -31,10 +32,7 @@ import java.util.Hashtable;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
-import java.util.StringTokenizer;
-import java.util.Vector;
import libcore.io.Base64;
-import org.apache.harmony.security.utils.JarUtils;
/**
* Non-public class used by {@link JarFile} and {@link JarInputStream} to manage
@@ -63,44 +61,42 @@ class JarVerifier {
};
private final String jarName;
+ private final Manifest manifest;
+ private final HashMap<String, byte[]> metaEntries;
+ private final int mainAttributesEnd;
- private Manifest man;
-
- private HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>(5);
-
- private final Hashtable<String, HashMap<String, Attributes>> signatures = new Hashtable<String, HashMap<String, Attributes>>(
- 5);
+ private final Hashtable<String, HashMap<String, Attributes>> signatures =
+ new Hashtable<String, HashMap<String, Attributes>>(5);
- private final Hashtable<String, Certificate[]> certificates = new Hashtable<String, Certificate[]>(
- 5);
+ private final Hashtable<String, Certificate[]> certificates =
+ new Hashtable<String, Certificate[]>(5);
- private final Hashtable<String, Certificate[]> verifiedEntries = new Hashtable<String, Certificate[]>();
-
- int mainAttributesEnd;
-
- /** Whether or not to check certificate chain signatures. */
- private final boolean chainCheck;
+ private final Hashtable<String, Certificate[][]> verifiedEntries =
+ new Hashtable<String, Certificate[][]>();
/**
* Stores and a hash and a message digest and verifies that massage digest
* matches the hash.
*/
- class VerifierEntry extends OutputStream {
+ static class VerifierEntry extends OutputStream {
- private String name;
+ private final String name;
- private MessageDigest digest;
+ private final MessageDigest digest;
- private byte[] hash;
+ private final byte[] hash;
- private Certificate[] certificates;
+ private final Certificate[][] certChains;
+
+ private final Hashtable<String, Certificate[][]> verifiedEntries;
VerifierEntry(String name, MessageDigest digest, byte[] hash,
- Certificate[] certificates) {
+ Certificate[][] certChains, Hashtable<String, Certificate[][]> verifedEntries) {
this.name = name;
this.digest = digest;
this.hash = hash;
- this.certificates = certificates;
+ this.certChains = certChains;
+ this.verifiedEntries = verifedEntries;
}
/**
@@ -122,7 +118,7 @@ class JarVerifier {
/**
* Verifies that the digests stored in the manifest match the decrypted
* digests from the .SF file. This indicates the validity of the
- * signing, not the integrity of the file, as it's digest must be
+ * signing, not the integrity of the file, as its digest must be
* calculated and verified when its contents are read.
*
* @throws SecurityException
@@ -133,40 +129,33 @@ class JarVerifier {
void verify() {
byte[] d = digest.digest();
if (!MessageDigest.isEqual(d, Base64.decode(hash))) {
- throw invalidDigest(JarFile.MANIFEST_NAME, name, jarName);
+ throw invalidDigest(JarFile.MANIFEST_NAME, name, name);
}
- verifiedEntries.put(name, certificates);
+ verifiedEntries.put(name, certChains);
}
-
}
- private SecurityException invalidDigest(String signatureFile, String name, String jarName) {
+ private static SecurityException invalidDigest(String signatureFile, String name,
+ String jarName) {
throw new SecurityException(signatureFile + " has invalid digest for " + name +
" in " + jarName);
}
- private SecurityException failedVerification(String jarName, String signatureFile) {
+ private static SecurityException failedVerification(String jarName, String signatureFile) {
throw new SecurityException(jarName + " failed verification of " + signatureFile);
}
/**
- * Convenience constructor for backward compatibility.
- */
- JarVerifier(String name) {
- this(name, false);
- }
-
- /**
* Constructs and returns a new instance of {@code JarVerifier}.
*
* @param name
* the name of the JAR file being verified.
- * @param chainCheck
- * whether to check the certificate chain signatures
*/
- JarVerifier(String name, boolean chainCheck) {
+ JarVerifier(String name, Manifest manifest, HashMap<String, byte[]> metaEntries) {
jarName = name;
- this.chainCheck = chainCheck;
+ this.manifest = manifest;
+ this.metaEntries = metaEntries;
+ this.mainAttributesEnd = manifest.getMainAttributesEnd();
}
/**
@@ -185,17 +174,17 @@ class JarVerifier {
// If no manifest is present by the time an entry is found,
// verification cannot occur. If no signature files have
// been found, do not verify.
- if (man == null || signatures.size() == 0) {
+ if (manifest == null || signatures.isEmpty()) {
return null;
}
- Attributes attributes = man.getAttributes(name);
+ Attributes attributes = manifest.getAttributes(name);
// entry has no digest
if (attributes == null) {
return null;
}
- ArrayList<Certificate> certs = new ArrayList<Certificate>();
+ ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>();
Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, HashMap<String, Attributes>> entry = it.next();
@@ -203,15 +192,18 @@ class JarVerifier {
if (hm.get(name) != null) {
// Found an entry for entry name in .SF file
String signatureFile = entry.getKey();
- certs.addAll(getSignerCertificates(signatureFile, certificates));
+ Certificate[] certChain = certificates.get(signatureFile);
+ if (certChain != null) {
+ certChains.add(certChain);
+ }
}
}
// entry is not signed
- if (certs.isEmpty()) {
+ if (certChains.isEmpty()) {
return null;
}
- Certificate[] certificatesArray = certs.toArray(new Certificate[certs.size()]);
+ Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]);
for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
final String algorithm = DIGEST_ALGORITHMS[i];
@@ -223,9 +215,8 @@ class JarVerifier {
try {
return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes,
- certificatesArray);
- } catch (NoSuchAlgorithmException e) {
- // ignored
+ certChainsArray, verifiedEntries);
+ } catch (NoSuchAlgorithmException ignored) {
}
}
return null;
@@ -266,18 +257,15 @@ class JarVerifier {
* corresponding signature file.
*/
synchronized boolean readCertificates() {
- if (metaEntries == null) {
+ if (metaEntries.isEmpty()) {
return false;
}
+
Iterator<String> it = metaEntries.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
verifyCertificate(key);
- // Check for recursive class load
- if (metaEntries == null) {
- return false;
- }
it.remove();
}
}
@@ -295,9 +283,9 @@ class JarVerifier {
return;
}
- byte[] manifest = metaEntries.get(JarFile.MANIFEST_NAME);
+ byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);
// Manifest entry is required for any verifications.
- if (manifest == null) {
+ if (manifestBytes == null) {
return;
}
@@ -305,15 +293,7 @@ class JarVerifier {
try {
Certificate[] signerCertChain = JarUtils.verifySignature(
new ByteArrayInputStream(sfBytes),
- new ByteArrayInputStream(sBlockBytes),
- chainCheck);
- /*
- * Recursive call in loading security provider related class which
- * is in a signed JAR.
- */
- if (metaEntries == null) {
- return;
- }
+ new ByteArrayInputStream(sBlockBytes));
if (signerCertChain != null) {
certificates.put(signatureFile, signerCertChain);
}
@@ -350,22 +330,22 @@ class JarVerifier {
// such verification.
if (mainAttributesEnd > 0 && !createdBySigntool) {
String digestAttribute = "-Digest-Manifest-Main-Attributes";
- if (!verify(attributes, digestAttribute, manifest, 0, mainAttributesEnd, false, true)) {
+ if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {
throw failedVerification(jarName, signatureFile);
}
}
// Use .SF to verify the whole manifest.
String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
- if (!verify(attributes, digestAttribute, manifest, 0, manifest.length, false, false)) {
+ if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {
Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Attributes> entry = it.next();
- Manifest.Chunk chunk = man.getChunk(entry.getKey());
+ Manifest.Chunk chunk = manifest.getChunk(entry.getKey());
if (chunk == null) {
return;
}
- if (!verify(entry.getValue(), "-Digest", manifest,
+ if (!verify(entry.getValue(), "-Digest", manifestBytes,
chunk.start, chunk.end, createdBySigntool, false)) {
throw invalidDigest(signatureFile, entry.getKey(), jarName);
}
@@ -376,16 +356,6 @@ class JarVerifier {
}
/**
- * Associate this verifier with the specified {@link Manifest} object.
- *
- * @param mf
- * a {@code java.util.jar.Manifest} object.
- */
- void setManifest(Manifest mf) {
- man = mf;
- }
-
- /**
* Returns a <code>boolean</code> indication of whether or not the
* associated jar file is signed.
*
@@ -424,58 +394,23 @@ class JarVerifier {
}
/**
- * Returns all of the {@link java.security.cert.Certificate} instances that
+ * Returns all of the {@link java.security.cert.Certificate} chains that
* were used to verify the signature on the JAR entry called
- * {@code name}.
+ * {@code name}. Callers must not modify the returned arrays.
*
* @param name
* the name of a JAR entry.
- * @return an array of {@link java.security.cert.Certificate}.
+ * @return an array of {@link java.security.cert.Certificate} chains.
*/
- Certificate[] getCertificates(String name) {
- Certificate[] verifiedCerts = verifiedEntries.get(name);
- if (verifiedCerts == null) {
- return null;
- }
- return verifiedCerts.clone();
+ Certificate[][] getCertificateChains(String name) {
+ return verifiedEntries.get(name);
}
/**
* Remove all entries from the internal collection of data held about each
* JAR entry in the {@code META-INF} directory.
- *
- * @see #addMetaEntry(String, byte[])
*/
void removeMetaEntries() {
- metaEntries = null;
- }
-
- /**
- * Returns a {@code Vector} of all of the
- * {@link java.security.cert.Certificate}s that are associated with the
- * signing of the named signature file.
- *
- * @param signatureFileName
- * the name of a signature file.
- * @param certificates
- * a {@code Map} of all of the certificate chains discovered so
- * far while attempting to verify the JAR that contains the
- * signature file {@code signatureFileName}. This object is
- * previously set in the course of one or more calls to
- * {@link #verifyJarSignatureFile(String, String, String, Map, Map)}
- * where it was passed as the last argument.
- * @return all of the {@code Certificate} entries for the signer of the JAR
- * whose actions led to the creation of the named signature file.
- */
- public static Vector<Certificate> getSignerCertificates(
- String signatureFileName, Map<String, Certificate[]> certificates) {
- Vector<Certificate> result = new Vector<Certificate>();
- Certificate[] certChain = certificates.get(signatureFileName);
- if (certChain != null) {
- for (Certificate element : certChain) {
- result.add(element);
- }
- }
- return result;
+ metaEntries.clear();
}
}
diff --git a/luni/src/main/java/java/util/jar/Manifest.java b/luni/src/main/java/java/util/jar/Manifest.java
index b6ebddc..6a3936d 100644
--- a/luni/src/main/java/java/util/jar/Manifest.java
+++ b/luni/src/main/java/java/util/jar/Manifest.java
@@ -17,11 +17,9 @@
package java.util.jar;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetEncoder;
@@ -43,26 +41,12 @@ public class Manifest implements Cloneable {
private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
- private static final Field BAIS_BUF = getByteArrayInputStreamField("buf");
- private static final Field BAIS_POS = getByteArrayInputStreamField("pos");
+ private final Attributes mainAttributes;
+ private final HashMap<String, Attributes> entries;
- private static Field getByteArrayInputStreamField(String name) {
- try {
- Field f = ByteArrayInputStream.class.getDeclaredField(name);
- f.setAccessible(true);
- return f;
- } catch (Exception ex) {
- throw new AssertionError(ex);
- }
- }
-
- private Attributes mainAttributes = new Attributes();
-
- private HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
-
- static class Chunk {
- int start;
- int end;
+ static final class Chunk {
+ final int start;
+ final int end;
Chunk(int start, int end) {
this.start = start;
@@ -82,6 +66,8 @@ public class Manifest implements Cloneable {
* Creates a new {@code Manifest} instance.
*/
public Manifest() {
+ entries = new HashMap<String, Attributes>();
+ mainAttributes = new Attributes();
}
/**
@@ -94,7 +80,8 @@ public class Manifest implements Cloneable {
* if an IO error occurs while creating this {@code Manifest}
*/
public Manifest(InputStream is) throws IOException {
- read(is);
+ this();
+ read(Streams.readFully(is));
}
/**
@@ -111,11 +98,12 @@ public class Manifest implements Cloneable {
.getEntries()).clone();
}
- Manifest(InputStream is, boolean readChunks) throws IOException {
+ Manifest(byte[] manifestBytes, boolean readChunks) throws IOException {
+ this();
if (readChunks) {
chunks = new HashMap<String, Chunk>();
}
- read(is);
+ read(manifestBytes);
}
/**
@@ -192,58 +180,20 @@ public class Manifest implements Cloneable {
* If an error occurs reading the manifest.
*/
public void read(InputStream is) throws IOException {
- byte[] buf;
- if (is instanceof ByteArrayInputStream) {
- buf = exposeByteArrayInputStreamBytes((ByteArrayInputStream) is);
- } else {
- buf = Streams.readFullyNoClose(is);
- }
+ read(Streams.readFullyNoClose(is));
+ }
+ private void read(byte[] buf) throws IOException {
if (buf.length == 0) {
return;
}
- // a workaround for HARMONY-5662
- // replace EOF and NUL with another new line
- // which does not trigger an error
- byte b = buf[buf.length - 1];
- if (b == 0 || b == 26) {
- buf[buf.length - 1] = '\n';
- }
-
ManifestReader im = new ManifestReader(buf, mainAttributes);
mainEnd = im.getEndOfMainSection();
im.readEntries(entries, chunks);
}
/**
- * Returns a byte[] containing all the bytes from a ByteArrayInputStream.
- * Where possible, this returns the actual array rather than a copy.
- */
- private static byte[] exposeByteArrayInputStreamBytes(ByteArrayInputStream bais) {
- byte[] buffer;
- synchronized (bais) {
- byte[] buf;
- int pos;
- try {
- buf = (byte[]) BAIS_BUF.get(bais);
- pos = BAIS_POS.getInt(bais);
- } catch (IllegalAccessException iae) {
- throw new AssertionError(iae);
- }
- int available = bais.available();
- if (pos == 0 && buf.length == available) {
- buffer = buf;
- } else {
- buffer = new byte[available];
- System.arraycopy(buf, pos, buffer, 0, available);
- }
- bais.skip(available);
- }
- return buffer;
- }
-
- /**
* Returns the hash code for this instance.
*
* @return this {@code Manifest}'s hashCode.
diff --git a/luni/src/main/java/java/util/jar/StrictJarFile.java b/luni/src/main/java/java/util/jar/StrictJarFile.java
new file mode 100644
index 0000000..4a8af5f
--- /dev/null
+++ b/luni/src/main/java/java/util/jar/StrictJarFile.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package java.util.jar;
+
+import dalvik.system.CloseGuard;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.security.cert.Certificate;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.zip.Inflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * A subset of the JarFile API implemented as a thin wrapper over
+ * system/core/libziparchive.
+ *
+ * @hide for internal use only. Not API compatible (or as forgiving) as
+ * {@link java.util.jar.JarFile}
+ */
+public final class StrictJarFile {
+
+ private final long nativeHandle;
+
+ // NOTE: It's possible to share a file descriptor with the native
+ // code, at the cost of some additional complexity.
+ private final RandomAccessFile raf;
+
+ private final Manifest manifest;
+ private final JarVerifier verifier;
+
+ private final boolean isSigned;
+
+ private final CloseGuard guard = CloseGuard.get();
+ private boolean closed;
+
+ public StrictJarFile(String fileName) throws IOException {
+ this.nativeHandle = nativeOpenJarFile(fileName);
+ this.raf = new RandomAccessFile(fileName, "r");
+
+ try {
+ // Read the MANIFEST and signature files up front and try to
+ // parse them. We never want to accept a JAR File with broken signatures
+ // or manifests, so it's best to throw as early as possible.
+ HashMap<String, byte[]> metaEntries = getMetaEntries();
+ this.manifest = new Manifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
+ this.verifier = new JarVerifier(fileName, manifest, metaEntries);
+
+ isSigned = verifier.readCertificates() && verifier.isSignedJar();
+ } catch (IOException ioe) {
+ nativeClose(this.nativeHandle);
+ throw ioe;
+ }
+
+ guard.open("close");
+ }
+
+ public Manifest getManifest() {
+ return manifest;
+ }
+
+ public Iterator<ZipEntry> iterator() throws IOException {
+ return new EntryIterator(nativeHandle, "");
+ }
+
+ public ZipEntry findEntry(String name) {
+ return nativeFindEntry(nativeHandle, name);
+ }
+
+ /**
+ * Return all certificate chains for a given {@link ZipEntry} belonging to this jar.
+ * This method MUST be called only after fully exhausting the InputStream belonging
+ * to this entry.
+ *
+ * Returns {@code null} if this jar file isn't signed or if this method is
+ * called before the stream is processed.
+ */
+ public Certificate[][] getCertificateChains(ZipEntry ze) {
+ if (isSigned) {
+ return verifier.getCertificateChains(ze.getName());
+ }
+
+ return null;
+ }
+
+ /**
+ * Return all certificates for a given {@link ZipEntry} belonging to this jar.
+ * This method MUST be called only after fully exhausting the InputStream belonging
+ * to this entry.
+ *
+ * Returns {@code null} if this jar file isn't signed or if this method is
+ * called before the stream is processed.
+ *
+ * @deprecated Switch callers to use getCertificateChains instead
+ */
+ @Deprecated
+ public Certificate[] getCertificates(ZipEntry ze) {
+ if (isSigned) {
+ Certificate[][] certChains = verifier.getCertificateChains(ze.getName());
+
+ // Measure number of certs.
+ int count = 0;
+ for (Certificate[] chain : certChains) {
+ count += chain.length;
+ }
+
+ // Create new array and copy all the certs into it.
+ Certificate[] certs = new Certificate[count];
+ int i = 0;
+ for (Certificate[] chain : certChains) {
+ System.arraycopy(chain, 0, certs, i, chain.length);
+ i += chain.length;
+ }
+
+ return certs;
+ }
+
+ return null;
+ }
+
+ public InputStream getInputStream(ZipEntry ze) {
+ final InputStream is = getZipInputStream(ze);
+
+ if (isSigned) {
+ JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
+ if (entry == null) {
+ return is;
+ }
+
+ return new JarFile.JarFileInputStream(is, ze.getSize(), entry);
+ }
+
+ return is;
+ }
+
+ public void close() throws IOException {
+ if (!closed) {
+ guard.close();
+
+ nativeClose(nativeHandle);
+ IoUtils.closeQuietly(raf);
+ closed = true;
+ }
+ }
+
+ private InputStream getZipInputStream(ZipEntry ze) {
+ if (ze.getMethod() == ZipEntry.STORED) {
+ return new ZipFile.RAFStream(raf, ze.getDataOffset(),
+ ze.getDataOffset() + ze.getSize());
+ } else {
+ final ZipFile.RAFStream wrapped = new ZipFile.RAFStream(
+ raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
+
+ int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
+ return new ZipFile.ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
+ }
+ }
+
+ static final class EntryIterator implements Iterator<ZipEntry> {
+ private final long iterationHandle;
+ private ZipEntry nextEntry;
+
+ EntryIterator(long nativeHandle, String prefix) throws IOException {
+ iterationHandle = nativeStartIteration(nativeHandle, prefix);
+ }
+
+ public ZipEntry next() {
+ if (nextEntry != null) {
+ final ZipEntry ze = nextEntry;
+ nextEntry = null;
+ return ze;
+ }
+
+ return nativeNextEntry(iterationHandle);
+ }
+
+ public boolean hasNext() {
+ if (nextEntry != null) {
+ return true;
+ }
+
+ final ZipEntry ze = nativeNextEntry(iterationHandle);
+ if (ze == null) {
+ return false;
+ }
+
+ nextEntry = ze;
+ return true;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private HashMap<String, byte[]> getMetaEntries() throws IOException {
+ HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
+
+ Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/");
+ while (entryIterator.hasNext()) {
+ final ZipEntry entry = entryIterator.next();
+ metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry)));
+ }
+
+ return metaEntries;
+ }
+
+ private static native long nativeOpenJarFile(String fileName) throws IOException;
+ private static native long nativeStartIteration(long nativeHandle, String prefix);
+ private static native ZipEntry nativeNextEntry(long iterationHandle);
+ private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
+ private static native void nativeClose(long nativeHandle);
+}
diff --git a/luni/src/main/java/java/util/logging/SocketHandler.java b/luni/src/main/java/java/util/logging/SocketHandler.java
index 85a9e6c..48bfc0e 100644
--- a/luni/src/main/java/java/util/logging/SocketHandler.java
+++ b/luni/src/main/java/java/util/logging/SocketHandler.java
@@ -108,12 +108,9 @@ public class SocketHandler extends StreamHandler {
// check the validity of the port number
int p = 0;
try {
- p = Integer.parseInt(port);
+ p = Integer.parsePositiveInt(port);
} catch (NumberFormatException e) {
- throw new IllegalArgumentException("Illegal port argument");
- }
- if (p <= 0) {
- throw new IllegalArgumentException("Illegal port argument");
+ throw new IllegalArgumentException("Illegal port argument " + port);
}
// establish the network connection
try {
diff --git a/luni/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java b/luni/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java
index 8f2d0aa..154c71e 100644
--- a/luni/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java
+++ b/luni/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java
@@ -24,10 +24,12 @@ package java.util.prefs;
*/
class FilePreferencesFactoryImpl implements PreferencesFactory {
// user root preferences
- private static final Preferences USER_ROOT = new FilePreferencesImpl(true);
+ private static final Preferences USER_ROOT = new FilePreferencesImpl(
+ System.getProperty("user.home") + "/.java/.userPrefs", true);
// system root preferences
- private static final Preferences SYSTEM_ROOT = new FilePreferencesImpl(false);
+ private static final Preferences SYSTEM_ROOT = new FilePreferencesImpl(
+ System.getProperty("java.home") + "/.systemPrefs", false);
public FilePreferencesFactoryImpl() {
}
@@ -39,5 +41,4 @@ class FilePreferencesFactoryImpl implements PreferencesFactory {
public Preferences systemRoot() {
return SYSTEM_ROOT;
}
-
}
diff --git a/luni/src/main/java/java/util/prefs/FilePreferencesImpl.java b/luni/src/main/java/java/util/prefs/FilePreferencesImpl.java
index bd367a6..de1ead4 100644
--- a/luni/src/main/java/java/util/prefs/FilePreferencesImpl.java
+++ b/luni/src/main/java/java/util/prefs/FilePreferencesImpl.java
@@ -30,20 +30,15 @@ import java.util.Set;
* TODO some sync mechanism with backend, Performance - check file edit date
*
* @since 1.4
+ *
+ * @hide
*/
-class FilePreferencesImpl extends AbstractPreferences {
-
+public class FilePreferencesImpl extends AbstractPreferences {
//prefs file name
private static final String PREFS_FILE_NAME = "prefs.xml";
- //home directory for user prefs
- private static String USER_HOME = System.getProperty("user.home") + "/.java/.userPrefs";
-
- //home directory for system prefs
- private static String SYSTEM_HOME = System.getProperty("java.home") + "/.systemPrefs";
-
//file path for this preferences node
- private String path;
+ private final String path;
//internal cache for prefs key-value pair
private Properties prefs;
@@ -67,13 +62,15 @@ class FilePreferencesImpl extends AbstractPreferences {
*/
/**
- * Construct root <code>FilePreferencesImpl</code> instance, construct
- * user root if userNode is true, system root otherwise
+ * Construct root <code>FilePreferencesImpl</code> instance rooted
+ * at the given path.
+ *
+ * @hide
*/
- FilePreferencesImpl(boolean userNode) {
+ public FilePreferencesImpl(String path, boolean isUserNode) {
super(null, "");
- this.userNode = userNode;
- path = userNode ? USER_HOME : SYSTEM_HOME;
+ this.path = path;
+ this.userNode = isUserNode;
initPrefs();
}
diff --git a/luni/src/main/java/java/util/prefs/Preferences.java b/luni/src/main/java/java/util/prefs/Preferences.java
index b808052..342be70 100644
--- a/luni/src/main/java/java/util/prefs/Preferences.java
+++ b/luni/src/main/java/java/util/prefs/Preferences.java
@@ -98,8 +98,17 @@ public abstract class Preferences {
*/
public static final int MAX_VALUE_LENGTH = 8192;
- //factory used to get user/system prefs root
- private static final PreferencesFactory factory = findPreferencesFactory();
+ // factory used to get user/system prefs root
+ private static volatile PreferencesFactory factory = findPreferencesFactory();
+
+ /**
+ * @hide for testing only.
+ */
+ public static PreferencesFactory setPreferencesFactory(PreferencesFactory pf) {
+ PreferencesFactory previous = factory;
+ factory = pf;
+ return previous;
+ }
private static PreferencesFactory findPreferencesFactory() {
// Try the system property first...
@@ -780,6 +789,11 @@ public abstract class Preferences {
public abstract void sync() throws BackingStoreException;
/**
+ * <strong>Legacy code; do not use.</strong> On Android, the Preference nodes
+ * corresponding to the "system" and "user" preferences are stored in sections
+ * of the file system that are inaccessible to apps. Further, allowing apps to set
+ * "system wide" preferences is contrary to android's security model.
+ *
* Returns the system preference node for the package of the given class.
* The absolute path of the returned node is one slash followed by the given
* class's full package name, replacing each period character ('.') with
@@ -796,11 +810,16 @@ public abstract class Preferences {
* @throws NullPointerException
* if the given class is {@code null}.
*/
- public static Preferences systemNodeForPackage (Class<?> c) {
+ public static Preferences systemNodeForPackage(Class<?> c) {
return factory.systemRoot().node(getNodeName(c));
}
/**
+ * <strong>Legacy code; do not use.</strong> On Android, the Preference nodes
+ * corresponding to the "system" and "user" preferences are stored in sections
+ * of the file system that are inaccessible to apps. Further, allowing apps to set
+ * "system wide" preferences is contrary to android's security model.
+ *
* Returns the root node of the system preference hierarchy.
*
* @return the system preference hierarchy root node.
@@ -810,6 +829,13 @@ public abstract class Preferences {
}
/**
+ *
+ * <strong>Legacy code; do not use.</strong> On Android, the Preference nodes
+ * corresponding to the "system" and "user" preferences are stored in sections
+ * of the file system that are inaccessible to apps. Further, allowing apps to set
+ * "system wide" preferences is contrary to android's security model.
+ *
+ * <p>
* Returns the user preference node for the package of the given class.
* The absolute path of the returned node is one slash followed by the given
* class's full package name, replacing each period character ('.') with
@@ -820,13 +846,11 @@ public abstract class Preferences {
* by this method won't necessarily be persisted until the method {@code
* flush()} is invoked.
*
- * @param c
- * the given class.
* @return the user preference node for the package of the given class.
* @throws NullPointerException
* if the given class is {@code null}.
*/
- public static Preferences userNodeForPackage (Class<?> c) {
+ public static Preferences userNodeForPackage(Class<?> c) {
return factory.userRoot().node(getNodeName(c));
}
@@ -840,6 +864,11 @@ public abstract class Preferences {
}
/**
+ * <strong>Legacy code; do not use.</strong> On Android, the Preference nodes
+ * corresponding to the "system" and "user" preferences are stored in sections
+ * of the file system that are inaccessible to apps. Further, allowing apps to set
+ * "system wide" preferences is contrary to android's security model.
+ *
* Returns the root node of the user preference hierarchy.
*
* @return the user preference hierarchy root node.
diff --git a/luni/src/main/java/java/util/regex/MatchResult.java b/luni/src/main/java/java/util/regex/MatchResult.java
index 76c17a8..c77206d 100644
--- a/luni/src/main/java/java/util/regex/MatchResult.java
+++ b/luni/src/main/java/java/util/regex/MatchResult.java
@@ -19,79 +19,65 @@ package java.util.regex;
/**
* Holds the results of a successful match of a {@link Pattern} against a
- * given string. The result is divided into groups, with one group for each
- * pair of parentheses in the regular expression and an additional group for
- * the whole regular expression. The start, end, and contents of each group
- * can be queried.
- *
- * @see Matcher
- * @see Matcher#toMatchResult()
+ * given string. Typically this is an instance of {@link Matcher}, but
+ * since that's a mutable class it's also possible to freeze its current
+ * state using {@link Matcher#toMatchResult}.
*/
public interface MatchResult {
/**
* Returns the index of the first character following the text that matched
* the whole regular expression.
- *
- * @return the character index.
*/
int end();
/**
* Returns the index of the first character following the text that matched
- * a given group.
- *
- * @param group
- * the group, ranging from 0 to groupCount() - 1, with 0
- * representing the whole pattern.
- *
- * @return the character index.
+ * a given group. See {@link #group} for an explanation of group indexes.
*/
int end(int group);
/**
* Returns the text that matched the whole regular expression.
- *
- * @return the text.
*/
String group();
/**
* Returns the text that matched a given group of the regular expression.
*
- * @param group
- * the group, ranging from 0 to groupCount() - 1, with 0
- * representing the whole pattern.
+ * <p>Explicit capturing groups in the pattern are numbered left to right in order
+ * of their <i>opening</i> parenthesis, starting at 1.
+ * The special group 0 represents the entire match (as if the entire pattern is surrounded
+ * by an implicit capturing group).
+ * For example, "a((b)c)" matching "abc" would give the following groups:
+ * <pre>
+ * 0 "abc"
+ * 1 "bc"
+ * 2 "b"
+ * </pre>
*
- * @return the text that matched the group.
+ * <p>An optional capturing group that failed to match as part of an overall
+ * successful match (for example, "a(b)?c" matching "ac") returns null.
+ * A capturing group that matched the empty string (for example, "a(b?)c" matching "ac")
+ * returns the empty string.
*/
String group(int group);
/**
- * Returns the number of groups in the result, which is always equal to
+ * Returns the number of groups in the results, which is always equal to
* the number of groups in the original regular expression.
- *
- * @return the number of groups.
*/
int groupCount();
/**
- * Returns the index of the first character of the text that matched
- * the whole regular expression.
- *
- * @return the character index.
+ * Returns the index of the first character of the text that matched the
+ * whole regular expression.
*/
int start();
/**
* Returns the index of the first character of the text that matched a given
- * group.
- *
- * @param group
- * the group, ranging from 0 to groupCount() - 1, with 0
- * representing the whole pattern.
- *
- * @return the character index.
+ * group. See {@link #group} for an explanation of group indexes.
*/
int start(int group);
}
diff --git a/luni/src/main/java/java/util/regex/MatchResultImpl.java b/luni/src/main/java/java/util/regex/MatchResultImpl.java
index b685757..6a0d948 100644
--- a/luni/src/main/java/java/util/regex/MatchResultImpl.java
+++ b/luni/src/main/java/java/util/regex/MatchResultImpl.java
@@ -32,7 +32,7 @@ class MatchResultImpl implements MatchResult {
/**
* Holds the offsets of the groups in the input text. The first two
- * elements specifiy start and end of the zero group, the next two specify
+ * elements specify start and end of the zero group, the next two specify
* group 1, and so on.
*/
private int[] offsets;
diff --git a/luni/src/main/java/java/util/regex/Matcher.java b/luni/src/main/java/java/util/regex/Matcher.java
index 02531d7..d181d45 100644
--- a/luni/src/main/java/java/util/regex/Matcher.java
+++ b/luni/src/main/java/java/util/regex/Matcher.java
@@ -276,8 +276,6 @@ public final class Matcher implements MatchResult {
* walk through the input and replace all matches of the {@code Pattern}
* with something else.
*
- * @param buffer
- * the {@code StringBuffer} to append to.
* @return the {@code StringBuffer}.
* @throws IllegalStateException
* if no successful match has been made.
@@ -325,57 +323,12 @@ public final class Matcher implements MatchResult {
/**
* Returns the {@link Pattern} instance used inside this matcher.
- *
- * @return the {@code Pattern} instance.
*/
public Pattern pattern() {
return pattern;
}
/**
- * Returns the text that matched a given group of the regular expression.
- * Explicit capturing groups in the pattern are numbered left to right in order
- * of their <i>opening</i> parenthesis, starting at 1.
- * The special group 0 represents the entire match (as if the entire pattern is surrounded
- * by an implicit capturing group).
- * For example, "a((b)c)" matching "abc" would give the following groups:
- * <pre>
- * 0 "abc"
- * 1 "bc"
- * 2 "b"
- * </pre>
- *
- * <p>An optional capturing group that failed to match as part of an overall
- * successful match (for example, "a(b)?c" matching "ac") returns null.
- * A capturing group that matched the empty string (for example, "a(b?)c" matching "ac")
- * returns the empty string.
- *
- * @throws IllegalStateException
- * if no successful match has been made.
- */
- public String group(int group) {
- ensureMatch();
- int from = matchOffsets[group * 2];
- int to = matchOffsets[(group * 2) + 1];
- if (from == -1 || to == -1) {
- return null;
- } else {
- return input.substring(from, to);
- }
- }
-
- /**
- * Returns the text that matched the whole regular expression.
- *
- * @return the text.
- * @throws IllegalStateException
- * if no successful match has been made.
- */
- public String group() {
- return group(0);
- }
-
- /**
* Returns true if there is another match in the input, starting
* from the given position. The region is ignored.
*
@@ -393,7 +346,7 @@ public final class Matcher implements MatchResult {
}
/**
- * Returns the next occurrence of the {@link Pattern} in the input. If a
+ * Moves to the next occurrence of the pattern in the input. If a
* previous match was successful, the method continues the search from the
* first character following that match in the input. Otherwise it searches
* either from the region start (if one has been set), or from position 0.
@@ -436,45 +389,8 @@ public final class Matcher implements MatchResult {
}
/**
- * Returns the index of the first character of the text that matched a given
- * group.
- *
- * @param group
- * the group, ranging from 0 to groupCount() - 1, with 0
- * representing the whole pattern.
- * @return the character index.
- * @throws IllegalStateException
- * if no successful match has been made.
- */
- public int start(int group) throws IllegalStateException {
- ensureMatch();
- return matchOffsets[group * 2];
- }
-
- /**
- * Returns the index of the first character following the text that matched
- * a given group.
- *
- * @param group
- * the group, ranging from 0 to groupCount() - 1, with 0
- * representing the whole pattern.
- * @return the character index.
- * @throws IllegalStateException
- * if no successful match has been made.
- */
- public int end(int group) {
- ensureMatch();
- return matchOffsets[(group * 2) + 1];
- }
-
- /**
* Returns a replacement string for the given one that has all backslashes
* and dollar signs escaped.
- *
- * @param s
- * the input string.
- * @return the input string, with all backslashes and dollar signs having
- * been escaped.
*/
public static String quoteReplacement(String s) {
StringBuilder result = new StringBuilder(s.length());
@@ -489,47 +405,10 @@ public final class Matcher implements MatchResult {
}
/**
- * Returns the index of the first character of the text that matched the
- * whole regular expression.
- *
- * @return the character index.
- * @throws IllegalStateException
- * if no successful match has been made.
- */
- public int start() {
- return start(0);
- }
-
- /**
- * Returns the number of groups in the results, which is always equal to
- * the number of groups in the original regular expression.
- *
- * @return the number of groups.
- */
- public int groupCount() {
- synchronized (this) {
- return groupCountImpl(address);
- }
- }
-
- /**
- * Returns the index of the first character following the text that matched
- * the whole regular expression.
- *
- * @return the character index.
- * @throws IllegalStateException
- * if no successful match has been made.
- */
- public int end() {
- return end(0);
- }
-
- /**
* Converts the current match into a separate {@link MatchResult} instance
* that is independent from this matcher. The new object is unaffected when
* the state of this matcher changes.
*
- * @return the new {@code MatchResult}.
* @throws IllegalStateException
* if no successful match has been made.
*/
@@ -544,8 +423,6 @@ public final class Matcher implements MatchResult {
* '^' and '$' meta-characters, otherwise not. Anchoring bounds are enabled
* by default.
*
- * @param value
- * the new value for anchoring bounds.
* @return the {@code Matcher} itself.
*/
public Matcher useAnchoringBounds(boolean value) {
@@ -572,8 +449,6 @@ public final class Matcher implements MatchResult {
* region are subject to lookahead and lookbehind, otherwise they are not.
* Transparent bounds are disabled by default.
*
- * @param value
- * the new value for transparent bounds.
* @return the {@code Matcher} itself.
*/
public Matcher useTransparentBounds(boolean value) {
@@ -666,6 +541,78 @@ public final class Matcher implements MatchResult {
" lastmatch=" + (matchFound ? group() : "") + "]";
}
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException if no successful match has been made.
+ */
+ public int end() {
+ return end(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException if no successful match has been made.
+ */
+ public int end(int group) {
+ ensureMatch();
+ return matchOffsets[(group * 2) + 1];
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException if no successful match has been made.
+ */
+ public String group() {
+ return group(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException if no successful match has been made.
+ */
+ public String group(int group) {
+ ensureMatch();
+ int from = matchOffsets[group * 2];
+ int to = matchOffsets[(group * 2) + 1];
+ if (from == -1 || to == -1) {
+ return null;
+ } else {
+ return input.substring(from, to);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int groupCount() {
+ synchronized (this) {
+ return groupCountImpl(address);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException if no successful match has been made.
+ */
+ public int start() {
+ return start(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException if no successful match has been made.
+ */
+ public int start(int group) throws IllegalStateException {
+ ensureMatch();
+ return matchOffsets[group * 2];
+ }
+
private static native void closeImpl(long addr);
private static native boolean findImpl(long addr, String s, int startIndex, int[] offsets);
private static native boolean findNextImpl(long addr, String s, int[] offsets);
diff --git a/luni/src/main/java/java/util/regex/Pattern.java b/luni/src/main/java/java/util/regex/Pattern.java
index a33ee93..8f3fb12 100644
--- a/luni/src/main/java/java/util/regex/Pattern.java
+++ b/luni/src/main/java/java/util/regex/Pattern.java
@@ -183,7 +183,7 @@ import java.io.Serializable;
* <tr> <td> <i>a</i>|<i>b</i> </td> <td>Either expression <i>a</i> or expression <i>b</i>.</td> </tr>
* </table>
*
- * <a name="flags"><h3>Flags</h3></a>
+ * <a name="flags"></a><h3>Flags</h3>
* <p><table>
* <tr> <td> (?dimsux-dimsux:<i>a</i>) </td> <td>Evaluates the expression <i>a</i> with the given flags enabled/disabled.</td> </tr>
* <tr> <td> (?dimsux-dimsux) </td> <td>Evaluates the rest of the pattern with the given flags enabled/disabled.</td> </tr>
diff --git a/luni/src/main/java/java/util/spi/CurrencyNameProvider.java b/luni/src/main/java/java/util/spi/CurrencyNameProvider.java
deleted file mode 100644
index d717aa2..0000000
--- a/luni/src/main/java/java/util/spi/CurrencyNameProvider.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.util.spi;
-
-import java.util.Locale;
-
-/**
- * This abstract class should be extended by service providers that provide
- * localized currency symbols (currency names) from currency codes.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class CurrencyNameProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected CurrencyNameProvider() {
- // do nothing
- }
-
- /**
- * Returns the localized currency symbol for the given currency code.
- *
- * @param code an ISO 4217 currency code
- * @param locale a locale
- * @return the symbol or null if there is no available symbol in the locale
- * @throws NullPointerException
- * if {@code code == null || locale == null}
- * @throws IllegalArgumentException
- * if code or locale is not in a legal format or not available
- */
- public abstract String getSymbol(String code, Locale locale);
-}
diff --git a/luni/src/main/java/java/util/spi/LocaleNameProvider.java b/luni/src/main/java/java/util/spi/LocaleNameProvider.java
deleted file mode 100644
index 0d25074..0000000
--- a/luni/src/main/java/java/util/spi/LocaleNameProvider.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.util.spi;
-
-import java.util.Locale;
-
-/**
- * This abstract class should be extended by service providers that provide
- * localized locale names.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class LocaleNameProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected LocaleNameProvider() {
- // do nothing
- }
-
- /**
- * Returns the localized name for the given ISO 639 language code.
- *
- * @param languageCode an ISO 639 language code
- * @param locale a locale
- * @return the name or null if unavailable
- * @throws NullPointerException
- * if {@code code == null || locale == null}
- * @throws IllegalArgumentException
- * if code or locale is not in a legal format or not available
- */
- public abstract String getDisplayLanguage(String languageCode, Locale locale);
-
- /**
- * Returns the localized name for the given ISO 3166 country code.
- *
- * @param countryCode an ISO 3166 language code
- * @param locale a locale
- * @return the name or null if unavailable
- * @throws NullPointerException
- * if {@code code == null || locale == null}
- * @throws IllegalArgumentException
- * if code or locale is not in a legal format or not available
- */
- public abstract String getDisplayCountry(String countryCode, Locale locale);
-
- /**
- * Returns the localized name for the given variant code.
- *
- * @param variantCode a variant code
- * @param locale a locale
- * @return the name or null if unavailable
- * @throws NullPointerException
- * if {@code code == null || locale == null}
- * @throws IllegalArgumentException
- * if code or locale is not in a legal format or not available
- */
- public abstract String getDisplayVariant(String variantCode, Locale locale);
-}
diff --git a/luni/src/main/java/java/util/spi/LocaleServiceProvider.java b/luni/src/main/java/java/util/spi/LocaleServiceProvider.java
deleted file mode 100644
index b1b62de..0000000
--- a/luni/src/main/java/java/util/spi/LocaleServiceProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.util.spi;
-
-import java.util.Locale;
-
-/**
- * The base class for all the locale related service provider interfaces (SPIs).
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected LocaleServiceProvider() {
- // do nothing
- }
-
- /**
- * Returns all locales for which this locale service provider has localized objects or names.
- */
- public abstract Locale[] getAvailableLocales();
-}
diff --git a/luni/src/main/java/java/util/spi/TimeZoneNameProvider.java b/luni/src/main/java/java/util/spi/TimeZoneNameProvider.java
deleted file mode 100644
index 533d14e..0000000
--- a/luni/src/main/java/java/util/spi/TimeZoneNameProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.util.spi;
-
-import java.util.Locale;
-
-/**
- * This abstract class should be extended by service providers that provide
- * localized time zone names.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class TimeZoneNameProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected TimeZoneNameProvider() {
- // do nothing
- }
-
- /**
- * Returns the localized name for the given time zone in the given locale.
- *
- * @param id the time zone id
- * @param daylight true to return the name for daylight saving time.
- * @param style TimeZone.LONG or TimeZone.SHORT
- * @param locale the locale
- * @return the readable time zone name, or null if it is unavailable
- * @throws NullPointerException
- * if {@code id == null || locale == null}
- * @throws IllegalArgumentException
- * if locale is not available or style is invalid
- */
- public abstract String getDisplayName(String id, boolean daylight, int style, Locale locale);
-}
diff --git a/luni/src/main/java/java/util/zip/Deflater.java b/luni/src/main/java/java/util/zip/Deflater.java
index 3365031..040058b 100644
--- a/luni/src/main/java/java/util/zip/Deflater.java
+++ b/luni/src/main/java/java/util/zip/Deflater.java
@@ -51,7 +51,7 @@ import libcore.util.EmptyArray;
* {@link DeflaterOutputStream} to handle all this for you. {@link DeflaterOutputStream} also helps
* minimize memory requirements&nbsp;&mdash; the sample code above is very expensive.
*
- * <a name="compression_level"><h3>Compression levels</h3></a>
+ * <a name="compression_level"></a><h3>Compression levels</h3>
* <p>A compression level must be {@link #DEFAULT_COMPRESSION} to compromise between speed and
* compression (currently equivalent to level 6), or between 0 ({@link #NO_COMPRESSION}, where
* the input is simply copied) and 9 ({@link #BEST_COMPRESSION}). Level 1 ({@link #BEST_SPEED})
@@ -347,7 +347,7 @@ public class Deflater {
/**
* Resets the {@code Deflater} to accept new input without affecting any
- * previously made settings for the compression strategy or level. This
+ * previous compression strategy or level settings. This
* operation <i>must</i> be called after {@link #finished} returns
* true if the {@code Deflater} is to be reused.
*/
@@ -417,7 +417,7 @@ public class Deflater {
* Sets the given <a href="#compression_level">compression level</a>
* to be used when compressing data. This value must be set
* prior to calling {@link #setInput setInput}.
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* If the compression level is invalid.
*/
public synchronized void setLevel(int level) {
@@ -435,7 +435,7 @@ public class Deflater {
* FILTERED, HUFFMAN_ONLY or DEFAULT_STRATEGY. This value must be set prior
* to calling {@link #setInput setInput}.
*
- * @exception IllegalArgumentException
+ * @throws IllegalArgumentException
* If the strategy specified is not one of FILTERED,
* HUFFMAN_ONLY or DEFAULT_STRATEGY.
*/
diff --git a/luni/src/main/java/java/util/zip/DeflaterInputStream.java b/luni/src/main/java/java/util/zip/DeflaterInputStream.java
index f987e39..16bf92f 100644
--- a/luni/src/main/java/java/util/zip/DeflaterInputStream.java
+++ b/luni/src/main/java/java/util/zip/DeflaterInputStream.java
@@ -133,17 +133,20 @@ public class DeflaterInputStream extends FilterInputStream {
def.setInput(buf, 0, bytesRead);
}
}
- int bytesDeflated = def.deflate(buf, 0, Math.min(buf.length, byteCount - count));
+ int bytesDeflated = def.deflate(buffer, byteOffset + count, byteCount - count);
if (bytesDeflated == -1) {
break;
}
- System.arraycopy(buf, 0, buffer, byteOffset + count, bytesDeflated);
count += bytesDeflated;
}
if (count == 0) {
count = -1;
available = false;
}
+
+ if (def.finished()) {
+ available = false;
+ }
return count;
}
diff --git a/luni/src/main/java/java/util/zip/DeflaterOutputStream.java b/luni/src/main/java/java/util/zip/DeflaterOutputStream.java
index 6cce5a5..3377afd 100644
--- a/luni/src/main/java/java/util/zip/DeflaterOutputStream.java
+++ b/luni/src/main/java/java/util/zip/DeflaterOutputStream.java
@@ -188,7 +188,10 @@ public class DeflaterOutputStream extends FilterOutputStream {
* Doing so may degrade compression but improve interactive behavior.
*/
@Override public void flush() throws IOException {
- if (syncFlush) {
+ // Though not documented, it's illegal to call deflate with any flush param
+ // other than Z_FINISH after the deflater has finished. See the error checking
+ // at the start of the deflate function in deflate.c.
+ if (syncFlush && !done) {
int byteCount;
while ((byteCount = def.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH)) != 0) {
out.write(buf, 0, byteCount);
diff --git a/luni/src/main/java/java/util/zip/GZIPInputStream.java b/luni/src/main/java/java/util/zip/GZIPInputStream.java
index 08599ea..1bfc496 100644
--- a/luni/src/main/java/java/util/zip/GZIPInputStream.java
+++ b/luni/src/main/java/java/util/zip/GZIPInputStream.java
@@ -20,9 +20,11 @@ package java.util.zip;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PushbackInputStream;
import java.nio.ByteOrder;
import java.util.Arrays;
import libcore.io.Memory;
+import libcore.io.Streams;
/**
* The {@code GZIPInputStream} class is used to read data stored in the GZIP
@@ -43,6 +45,9 @@ import libcore.io.Memory;
* zis.close();
* }
* </pre>
+ *
+ * <p>Note that this class ignores all remaining data at the end of the last
+ * GZIP member.
*/
public class GZIPInputStream extends InflaterInputStream {
private static final int FCOMMENT = 16;
@@ -53,6 +58,8 @@ public class GZIPInputStream extends InflaterInputStream {
private static final int FNAME = 8;
+ private static final int GZIP_TRAILER_SIZE = 8;
+
/**
* The magic header for the GZIP format.
*/
@@ -94,48 +101,18 @@ public class GZIPInputStream extends InflaterInputStream {
*/
public GZIPInputStream(InputStream is, int size) throws IOException {
super(is, new Inflater(true), size);
- byte[] header = new byte[10];
- readFully(header, 0, header.length);
- short magic = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN);
- if (magic != (short) GZIP_MAGIC) {
- throw new IOException(String.format("unknown format (magic number %x)", magic));
- }
- int flags = header[3];
- boolean hcrc = (flags & FHCRC) != 0;
- if (hcrc) {
- crc.update(header, 0, header.length);
- }
- if ((flags & FEXTRA) != 0) {
- readFully(header, 0, 2);
- if (hcrc) {
- crc.update(header, 0, 2);
- }
- int length = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN) & 0xffff;
- while (length > 0) {
- int max = length > buf.length ? buf.length : length;
- int result = in.read(buf, 0, max);
- if (result == -1) {
- throw new EOFException();
- }
- if (hcrc) {
- crc.update(buf, 0, result);
- }
- length -= result;
- }
- }
- if ((flags & FNAME) != 0) {
- readZeroTerminated(hcrc);
- }
- if ((flags & FCOMMENT) != 0) {
- readZeroTerminated(hcrc);
- }
- if (hcrc) {
- readFully(header, 0, 2);
- short crc16 = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN);
- if ((short) crc.getValue() != crc16) {
- throw new IOException("CRC mismatch");
+
+ try {
+ byte[] header = readHeader(is);
+ final short magic = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN);
+ if (magic != (short) GZIP_MAGIC) {
+ throw new IOException(String.format("unknown format (magic number %x)", magic));
}
- crc.reset();
+
+ parseGzipHeader(is, header, crc, buf);
+ } catch (IOException e) {
+ close(); // release the inflater
+ throw e;
}
}
@@ -171,11 +148,109 @@ public class GZIPInputStream extends InflaterInputStream {
if (eos) {
verifyCrc();
+ eos = maybeReadNextMember();
+ if (!eos) {
+ crc.reset();
+ inf.reset();
+ eof = false;
+ len = 0;
+ }
}
return bytesRead;
}
+ private boolean maybeReadNextMember() throws IOException {
+ // If we have any unconsumed data in the inflater buffer, we have to
+ // scan that first. The fact that we've reached here implies we've
+ // successfully consumed the GZIP trailer.
+ final int remaining = inf.getRemaining() - GZIP_TRAILER_SIZE;
+ if (remaining > 0) {
+ // NOTE: We make sure we create a pushback stream exactly once,
+ // even if the input stream contains multiple members.
+ //
+ // The push back stream we create must therefore be able to contain
+ // (worst case) the entire buffer even though there may be fewer bytes
+ // remaining when it is first created.
+ if (!(in instanceof PushbackInputStream)) {
+ in = new PushbackInputStream(in, buf.length);
+ }
+ ((PushbackInputStream) in).unread(buf,
+ inf.getCurrentOffset() + GZIP_TRAILER_SIZE, remaining);
+ }
+
+ final byte[] buffer;
+ try {
+ buffer = readHeader(in);
+ } catch (EOFException eof) {
+ // We've reached the end of the stream and there are no more members
+ // to read. Note that we might also hit this if there are fewer than
+ // GZIP_HEADER_LENGTH bytes at the end of a member. We don't care
+ // because we're specified to ignore all data at the end of the last
+ // gzip record.
+ return true;
+ }
+
+ final short magic = Memory.peekShort(buffer, 0, ByteOrder.LITTLE_ENDIAN);
+ if (magic != (short) GZIP_MAGIC) {
+ // Don't throw here because we've already read one valid member
+ // from this stream.
+ return true;
+ }
+
+ // We've encountered the gzip magic number, so we assume there's another
+ // member in the stream.
+ parseGzipHeader(in, buffer, crc, buf);
+ return false;
+ }
+
+ private static byte[] readHeader(InputStream in) throws IOException {
+ byte[] header = new byte[10];
+ Streams.readFully(in, header, 0, header.length);
+ return header;
+ }
+
+ private static void parseGzipHeader(InputStream in, byte[] header,
+ CRC32 crc, byte[] scratch) throws IOException {
+ final byte flags = header[3];
+ final boolean hcrc = (flags & FHCRC) != 0;
+ if (hcrc) {
+ crc.update(header, 0, header.length);
+ }
+ if ((flags & FEXTRA) != 0) {
+ Streams.readFully(in, header, 0, 2);
+ if (hcrc) {
+ crc.update(header, 0, 2);
+ }
+ int length = Memory.peekShort(scratch, 0, ByteOrder.LITTLE_ENDIAN) & 0xffff;
+ while (length > 0) {
+ int max = length > scratch.length ? scratch.length : length;
+ int result = in.read(scratch, 0, max);
+ if (result == -1) {
+ throw new EOFException();
+ }
+ if (hcrc) {
+ crc.update(scratch, 0, result);
+ }
+ length -= result;
+ }
+ }
+ if ((flags & FNAME) != 0) {
+ readZeroTerminated(in, crc, hcrc);
+ }
+ if ((flags & FCOMMENT) != 0) {
+ readZeroTerminated(in, crc, hcrc);
+ }
+ if (hcrc) {
+ Streams.readFully(in, header, 0, 2);
+ short crc16 = Memory.peekShort(scratch, 0, ByteOrder.LITTLE_ENDIAN);
+ if ((short) crc.getValue() != crc16) {
+ throw new IOException("CRC mismatch");
+ }
+ crc.reset();
+ }
+ }
+
private void verifyCrc() throws IOException {
// Get non-compressed bytes read by fill
int size = inf.getRemaining();
@@ -184,7 +259,7 @@ public class GZIPInputStream extends InflaterInputStream {
int copySize = (size > trailerSize) ? trailerSize : size;
System.arraycopy(buf, len - size, b, 0, copySize);
- readFully(b, copySize, trailerSize - copySize);
+ Streams.readFully(in, b, copySize, trailerSize - copySize);
if (Memory.peekInt(b, 0, ByteOrder.LITTLE_ENDIAN) != (int) crc.getValue()) {
throw new IOException("CRC mismatch");
@@ -194,20 +269,11 @@ public class GZIPInputStream extends InflaterInputStream {
}
}
- private void readFully(byte[] buffer, int offset, int length) throws IOException {
- int result;
- while (length > 0) {
- result = in.read(buffer, offset, length);
- if (result == -1) {
- throw new EOFException();
- }
- offset += result;
- length -= result;
- }
- }
-
- private void readZeroTerminated(boolean hcrc) throws IOException {
+ private static void readZeroTerminated(InputStream in, CRC32 crc, boolean hcrc)
+ throws IOException {
int result;
+ // TODO: Fix these single byte reads. This method is used to consume the
+ // header FNAME & FCOMMENT which aren't widely used in gzip files.
while ((result = in.read()) > 0) {
if (hcrc) {
crc.update(result);
diff --git a/luni/src/main/java/java/util/zip/GZIPOutputStream.java b/luni/src/main/java/java/util/zip/GZIPOutputStream.java
index 8dd907b..0111292 100644
--- a/luni/src/main/java/java/util/zip/GZIPOutputStream.java
+++ b/luni/src/main/java/java/util/zip/GZIPOutputStream.java
@@ -51,7 +51,7 @@ public class GZIPOutputStream extends DeflaterOutputStream {
* the given stream.
*/
public GZIPOutputStream(OutputStream os) throws IOException {
- this(os, BUF_SIZE, true);
+ this(os, BUF_SIZE, false);
}
/**
@@ -65,11 +65,10 @@ public class GZIPOutputStream extends DeflaterOutputStream {
/**
* Constructs a new {@code GZIPOutputStream} to write data in GZIP format to
- * the given stream with the given internal buffer size and
- * flushing behavior (see {@link DeflaterOutputStream#flush}).
+ * the given stream with the given internal buffer size.
*/
public GZIPOutputStream(OutputStream os, int bufferSize) throws IOException {
- this(os, bufferSize, true);
+ this(os, bufferSize, false);
}
/**
diff --git a/luni/src/main/java/java/util/zip/Inflater.java b/luni/src/main/java/java/util/zip/Inflater.java
index ee10aa7..581ed94 100644
--- a/luni/src/main/java/java/util/zip/Inflater.java
+++ b/luni/src/main/java/java/util/zip/Inflater.java
@@ -170,6 +170,15 @@ public class Inflater {
}
/**
+ * Returns the offset of the next byte to read in the underlying buffer.
+ *
+ * For internal use only.
+ */
+ synchronized int getCurrentOffset() {
+ return inRead;
+ }
+
+ /**
* Returns the total number of bytes of input read by this {@code Inflater}. This
* method is limited to 32 bits; use {@link #getBytesRead} instead.
*/
diff --git a/luni/src/main/java/java/util/zip/InflaterInputStream.java b/luni/src/main/java/java/util/zip/InflaterInputStream.java
index 25b2fe8..e5ad3db 100644
--- a/luni/src/main/java/java/util/zip/InflaterInputStream.java
+++ b/luni/src/main/java/java/util/zip/InflaterInputStream.java
@@ -266,12 +266,7 @@ public class InflaterInputStream extends FilterInputStream {
}
/**
- * Reset the position of the stream to the last marked position. This
- * implementation overrides the supertype implementation and always throws
- * an {@link IOException IOException} when called.
- *
- * @throws IOException
- * if the method is called
+ * This operation is not supported and throws {@code IOException}.
*/
@Override
public void reset() throws IOException {
diff --git a/luni/src/main/java/java/util/zip/ZipEntry.java b/luni/src/main/java/java/util/zip/ZipEntry.java
index f64c717..217cc3c 100644
--- a/luni/src/main/java/java/util/zip/ZipEntry.java
+++ b/luni/src/main/java/java/util/zip/ZipEntry.java
@@ -20,14 +20,15 @@ package java.util.zip;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
-import libcore.io.Streams;
import libcore.io.BufferIterator;
import libcore.io.HeapBufferIterator;
+import libcore.io.Streams;
/**
* An entry within a zip file.
@@ -54,6 +55,8 @@ public class ZipEntry implements ZipConstants, Cloneable {
int nameLength = -1;
long localHeaderRelOffset = -1;
+ long dataOffset = -1;
+
/**
* Zip entry state: Deflated.
*/
@@ -64,6 +67,23 @@ public class ZipEntry implements ZipConstants, Cloneable {
*/
public static final int STORED = 0;
+ ZipEntry(String name, String comment, long crc, long compressedSize,
+ long size, int compressionMethod, int time, int modDate, byte[] extra,
+ int nameLength, long localHeaderRelOffset, long dataOffset) {
+ this.name = name;
+ this.comment = comment;
+ this.crc = crc;
+ this.compressedSize = compressedSize;
+ this.size = size;
+ this.compressionMethod = compressionMethod;
+ this.time = time;
+ this.modDate = modDate;
+ this.extra = extra;
+ this.nameLength = nameLength;
+ this.localHeaderRelOffset = localHeaderRelOffset;
+ this.dataOffset = dataOffset;
+ }
+
/**
* Constructs a new {@code ZipEntry} with the specified name. The name is actually a path,
* and may contain {@code /} characters.
@@ -75,9 +95,7 @@ public class ZipEntry implements ZipConstants, Cloneable {
if (name == null) {
throw new NullPointerException("name == null");
}
- if (name.length() > 0xFFFF) {
- throw new IllegalArgumentException("Name too long: " + name.length());
- }
+ validateStringLength("Name", name);
this.name = name;
}
@@ -184,11 +202,8 @@ public class ZipEntry implements ZipConstants, Cloneable {
this.comment = null;
return;
}
+ validateStringLength("Comment", comment);
- byte[] commentBytes = comment.getBytes(StandardCharsets.UTF_8);
- if (commentBytes.length > 0xffff) {
- throw new IllegalArgumentException("Comment too long: " + commentBytes.length);
- }
this.comment = comment;
}
@@ -287,6 +302,17 @@ public class ZipEntry implements ZipConstants, Cloneable {
}
}
+
+ /** @hide */
+ public void setDataOffset(long value) {
+ dataOffset = value;
+ }
+
+ /** @hide */
+ public long getDataOffset() {
+ return dataOffset;
+ }
+
/**
* Returns the string representation of this {@code ZipEntry}.
*
@@ -316,6 +342,7 @@ public class ZipEntry implements ZipConstants, Cloneable {
extra = ze.extra;
nameLength = ze.nameLength;
localHeaderRelOffset = ze.localHeaderRelOffset;
+ dataOffset = ze.dataOffset;
}
/**
@@ -344,12 +371,14 @@ public class ZipEntry implements ZipConstants, Cloneable {
/*
* Internal constructor. Creates a new ZipEntry by reading the
* Central Directory Entry (CDE) from "in", which must be positioned
- * at the CDE signature.
+ * at the CDE signature. If the GPBF_UTF8_FLAG is set in the CDE then
+ * UTF-8 is used to decode the string information, otherwise the
+ * defaultCharset is used.
*
* On exit, "in" will be positioned at the start of the next entry
* in the Central Directory.
*/
- ZipEntry(byte[] cdeHdrBuf, InputStream cdStream) throws IOException {
+ ZipEntry(byte[] cdeHdrBuf, InputStream cdStream, Charset defaultCharset) throws IOException {
Streams.readFully(cdStream, cdeHdrBuf, 0, cdeHdrBuf.length);
BufferIterator it = HeapBufferIterator.iterator(cdeHdrBuf, 0, cdeHdrBuf.length,
@@ -367,6 +396,13 @@ public class ZipEntry implements ZipConstants, Cloneable {
throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf);
}
+ // If the GPBF_UTF8_FLAG is set then the character encoding is UTF-8 whatever the default
+ // provided.
+ Charset charset = defaultCharset;
+ if ((gpbf & ZipFile.GPBF_UTF8_FLAG) != 0) {
+ charset = StandardCharsets.UTF_8;
+ }
+
compressionMethod = it.readShort() & 0xffff;
time = it.readShort() & 0xffff;
modDate = it.readShort() & 0xffff;
@@ -389,19 +425,17 @@ public class ZipEntry implements ZipConstants, Cloneable {
if (containsNulByte(nameBytes)) {
throw new ZipException("Filename contains NUL byte: " + Arrays.toString(nameBytes));
}
- name = new String(nameBytes, 0, nameBytes.length, StandardCharsets.UTF_8);
+ name = new String(nameBytes, 0, nameBytes.length, charset);
if (extraLength > 0) {
extra = new byte[extraLength];
Streams.readFully(cdStream, extra, 0, extraLength);
}
- // The RI has always assumed UTF-8. (If GPBF_UTF8_FLAG isn't set, the encoding is
- // actually IBM-437.)
if (commentByteCount > 0) {
byte[] commentBytes = new byte[commentByteCount];
Streams.readFully(cdStream, commentBytes, 0, commentByteCount);
- comment = new String(commentBytes, 0, commentBytes.length, StandardCharsets.UTF_8);
+ comment = new String(commentBytes, 0, commentBytes.length, charset);
}
}
@@ -413,4 +447,14 @@ public class ZipEntry implements ZipConstants, Cloneable {
}
return false;
}
+
+ private static void validateStringLength(String argument, String string) {
+ // This check is not perfect: the character encoding is determined when the entry is
+ // written out. UTF-8 is probably a worst-case: most alternatives should be single byte per
+ // character.
+ byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
+ if (bytes.length > 0xffff) {
+ throw new IllegalArgumentException(argument + " too long: " + bytes.length);
+ }
+ }
}
diff --git a/luni/src/main/java/java/util/zip/ZipFile.java b/luni/src/main/java/java/util/zip/ZipFile.java
index c25bbc1..b44156e 100644
--- a/luni/src/main/java/java/util/zip/ZipFile.java
+++ b/luni/src/main/java/java/util/zip/ZipFile.java
@@ -32,6 +32,7 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import libcore.io.BufferIterator;
import libcore.io.HeapBufferIterator;
+import libcore.io.IoUtils;
import libcore.io.Streams;
/**
@@ -108,6 +109,9 @@ public class ZipFile implements Closeable, ZipConstants {
/**
* Constructs a new {@code ZipFile} allowing read access to the contents of the given file.
+ *
+ * <p>UTF-8 is used to decode all comments and entry names in the file.
+ *
* @throws ZipException if a zip error occurs.
* @throws IOException if an {@code IOException} occurs.
*/
@@ -117,6 +121,9 @@ public class ZipFile implements Closeable, ZipConstants {
/**
* Constructs a new {@code ZipFile} allowing read access to the contents of the given file.
+ *
+ * <p>UTF-8 is used to decode all comments and entry names in the file.
+ *
* @throws IOException if an IOException occurs.
*/
public ZipFile(String name) throws IOException {
@@ -125,9 +132,11 @@ public class ZipFile implements Closeable, ZipConstants {
/**
* Constructs a new {@code ZipFile} allowing access to the given file.
- * The {@code mode} must be either {@code OPEN_READ} or {@code OPEN_READ|OPEN_DELETE}.
*
- * <p>If the {@code OPEN_DELETE} flag is supplied, the file will be deleted at or before the
+ * <p>UTF-8 is used to decode all comments and entry names in the file.
+ *
+ * <p>The {@code mode} must be either {@code OPEN_READ} or {@code OPEN_READ|OPEN_DELETE}.
+ * If the {@code OPEN_DELETE} flag is supplied, the file will be deleted at or before the
* time that the {@code ZipFile} is closed (the contents will remain accessible until
* this {@code ZipFile} is closed); it also calls {@code File.deleteOnExit}.
*
@@ -148,7 +157,19 @@ public class ZipFile implements Closeable, ZipConstants {
raf = new RandomAccessFile(filename, "r");
- readCentralDir();
+ // Make sure to close the RandomAccessFile if reading the central directory fails.
+ boolean mustCloseFile = true;
+ try {
+ readCentralDir();
+
+ // Read succeeded so do not close the underlying RandomAccessFile.
+ mustCloseFile = false;
+ } finally {
+ if (mustCloseFile) {
+ IoUtils.closeQuietly(raf);
+ }
+ }
+
guard.open("close");
}
@@ -357,6 +378,9 @@ public class ZipFile implements Closeable, ZipConstants {
raf.seek(0);
final int headerMagic = Integer.reverseBytes(raf.readInt());
+ if (headerMagic == ENDSIG) {
+ throw new ZipException("Empty zip archive not supported");
+ }
if (headerMagic != LOCSIG) {
throw new ZipException("Not a zip archive");
}
@@ -411,7 +435,7 @@ public class ZipFile implements Closeable, ZipConstants {
BufferedInputStream bufferedStream = new BufferedInputStream(rafStream, 4096);
byte[] hdrBuf = new byte[CENHDR]; // Reuse the same buffer for each entry.
for (int i = 0; i < numEntries; ++i) {
- ZipEntry newEntry = new ZipEntry(hdrBuf, bufferedStream);
+ ZipEntry newEntry = new ZipEntry(hdrBuf, bufferedStream, StandardCharsets.UTF_8);
if (newEntry.localHeaderRelOffset >= centralDirOffset) {
throw new ZipException("Local file header offset is after central directory");
}
@@ -434,16 +458,23 @@ public class ZipFile implements Closeable, ZipConstants {
* collisions.)
*
* <p>We could support mark/reset, but we don't currently need them.
+ *
+ * @hide
*/
- static class RAFStream extends InputStream {
+ public static class RAFStream extends InputStream {
private final RandomAccessFile sharedRaf;
private long endOffset;
private long offset;
- public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
+
+ public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) {
sharedRaf = raf;
offset = initialOffset;
- endOffset = raf.length();
+ this.endOffset = endOffset;
+ }
+
+ public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
+ this(raf, initialOffset, raf.length());
}
@Override public int available() throws IOException {
@@ -491,7 +522,8 @@ public class ZipFile implements Closeable, ZipConstants {
}
}
- static class ZipInflaterInputStream extends InflaterInputStream {
+ /** @hide */
+ public static class ZipInflaterInputStream extends InflaterInputStream {
private final ZipEntry entry;
private long bytesRead = 0;
diff --git a/luni/src/main/java/java/util/zip/ZipInputStream.java b/luni/src/main/java/java/util/zip/ZipInputStream.java
index 9c18f49..4c0034e 100644
--- a/luni/src/main/java/java/util/zip/ZipInputStream.java
+++ b/luni/src/main/java/java/util/zip/ZipInputStream.java
@@ -22,8 +22,7 @@ import java.io.InputStream;
import java.io.PushbackInputStream;
import java.nio.ByteOrder;
import java.nio.charset.ModifiedUtf8;
-import java.util.jar.Attributes;
-import java.util.jar.JarEntry;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import libcore.io.Memory;
import libcore.io.Streams;
@@ -86,12 +85,14 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants
private final CRC32 crc = new CRC32();
- private byte[] nameBuf = new byte[256];
+ private byte[] stringBytesBuf = new byte[256];
- private char[] charBuf = new char[256];
+ private char[] stringCharBuf = new char[256];
/**
* Constructs a new {@code ZipInputStream} to read zip entries from the given input stream.
+ *
+ * <p>UTF-8 is used to decode all strings in the file.
*/
public ZipInputStream(InputStream stream) {
super(new PushbackInputStream(stream, BUF_SIZE), new Inflater(true));
@@ -125,12 +126,6 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants
if (currentEntry == null) {
return;
}
- if (currentEntry instanceof java.util.jar.JarEntry) {
- Attributes temp = ((JarEntry) currentEntry).getAttributes();
- if (temp != null && temp.containsKey("hidden")) {
- return;
- }
- }
/*
* The following code is careful to leave the ZipInputStream in a
@@ -257,14 +252,8 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants
}
int extraLength = peekShort(LOCEXT - LOCVER);
- if (nameLength > nameBuf.length) {
- nameBuf = new byte[nameLength];
- // The bytes are modified UTF-8, so the number of chars will always be less than or
- // equal to the number of bytes. It's fine if this buffer is too long.
- charBuf = new char[nameLength];
- }
- Streams.readFully(in, nameBuf, 0, nameLength);
- currentEntry = createZipEntry(ModifiedUtf8.decode(nameBuf, charBuf, 0, nameLength));
+ String name = readString(nameLength);
+ currentEntry = createZipEntry(name);
currentEntry.time = ceLastModifiedTime;
currentEntry.modDate = ceLastModifiedDate;
currentEntry.setMethod(ceCompressionMethod);
@@ -281,6 +270,22 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants
return currentEntry;
}
+ /**
+ * Reads bytes from the current stream position returning the string representation.
+ */
+ private String readString(int byteLength) throws IOException {
+ if (byteLength > stringBytesBuf.length) {
+ stringBytesBuf = new byte[byteLength];
+ }
+ Streams.readFully(in, stringBytesBuf, 0, byteLength);
+ // The number of chars will always be less than or equal to the number of bytes. It's
+ // fine if this buffer is too long.
+ if (byteLength > stringCharBuf.length) {
+ stringCharBuf = new char[byteLength];
+ }
+ return ModifiedUtf8.decode(stringBytesBuf, stringCharBuf, 0, byteLength);
+ }
+
private int peekShort(int offset) {
return Memory.peekShort(hdrBuf, offset, ByteOrder.LITTLE_ENDIAN) & 0xffff;
}
diff --git a/luni/src/main/java/java/util/zip/ZipOutputStream.java b/luni/src/main/java/java/util/zip/ZipOutputStream.java
index 04de03f..8278355 100644
--- a/luni/src/main/java/java/util/zip/ZipOutputStream.java
+++ b/luni/src/main/java/java/util/zip/ZipOutputStream.java
@@ -85,13 +85,19 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
private final CRC32 crc = new CRC32();
- private int offset = 0, curOffset = 0, nameLength;
+ private int offset = 0, curOffset = 0;
+ /** The charset-encoded name for the current entry. */
private byte[] nameBytes;
+ /** The charset-encoded comment for the current entry. */
+ private byte[] entryCommentBytes;
+
/**
- * Constructs a new {@code ZipOutputStream} that writes a zip file
- * to the given {@code OutputStream}.
+ * Constructs a new {@code ZipOutputStream} that writes a zip file to the given
+ * {@code OutputStream}.
+ *
+ * <p>UTF-8 will be used to encode the file comment, entry names and comments.
*/
public ZipOutputStream(OutputStream os) {
super(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
@@ -153,8 +159,8 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
// Update the CentralDirectory
// http://www.pkware.com/documents/casestudies/APPNOTE.TXT
int flags = currentEntry.getMethod() == STORED ? 0 : ZipFile.GPBF_DATA_DESCRIPTOR_FLAG;
- // Since gingerbread, we always set the UTF-8 flag on individual files.
- // Some tools insist that the central directory also have the UTF-8 flag.
+ // Since gingerbread, we always set the UTF-8 flag on individual files if appropriate.
+ // Some tools insist that the central directory have the UTF-8 flag.
// http://code.google.com/p/android/issues/detail?id=20214
flags |= ZipFile.GPBF_UTF8_FLAG;
writeLong(cDir, CENSIG);
@@ -172,19 +178,14 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
curOffset += writeLong(cDir, crc.tbytes);
writeLong(cDir, crc.tbytes);
}
- curOffset += writeShort(cDir, nameLength);
+ curOffset += writeShort(cDir, nameBytes.length);
if (currentEntry.extra != null) {
curOffset += writeShort(cDir, currentEntry.extra.length);
} else {
writeShort(cDir, 0);
}
- String comment = currentEntry.getComment();
- byte[] commentBytes = EmptyArray.BYTE;
- if (comment != null) {
- commentBytes = comment.getBytes(StandardCharsets.UTF_8);
- }
- writeShort(cDir, commentBytes.length); // Comment length.
+ writeShort(cDir, entryCommentBytes.length); // Comment length.
writeShort(cDir, 0); // Disk Start
writeShort(cDir, 0); // Internal File Attributes
writeLong(cDir, 0); // External File Attributes
@@ -195,8 +196,9 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
cDir.write(currentEntry.extra);
}
offset += curOffset;
- if (commentBytes.length > 0) {
- cDir.write(commentBytes);
+ if (entryCommentBytes.length > 0) {
+ cDir.write(entryCommentBytes);
+ entryCommentBytes = EmptyArray.BYTE;
}
currentEntry = null;
crc.reset();
@@ -295,9 +297,13 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
throw new ZipException("Too many entries for the zip file format's 16-bit entry count");
}
nameBytes = ze.name.getBytes(StandardCharsets.UTF_8);
- nameLength = nameBytes.length;
- if (nameLength > 0xffff) {
- throw new IllegalArgumentException("Name too long: " + nameLength + " UTF-8 bytes");
+ checkSizeIsWithinShort("Name", nameBytes);
+ entryCommentBytes = EmptyArray.BYTE;
+ if (ze.comment != null) {
+ entryCommentBytes = ze.comment.getBytes(StandardCharsets.UTF_8);
+ // The comment is not written out until the entry is finished, but it is validated here
+ // to fail-fast.
+ checkSizeIsWithinShort("Comment", entryCommentBytes);
}
def.setLevel(compressionLevel);
@@ -310,7 +316,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
// http://www.pkware.com/documents/casestudies/APPNOTE.TXT
int flags = (method == STORED) ? 0 : ZipFile.GPBF_DATA_DESCRIPTOR_FLAG;
// Java always outputs UTF-8 filenames. (Before Java 7, the RI didn't set this flag and used
- // modified UTF-8. From Java 7, it sets this flag and uses normal UTF-8.)
+ // modified UTF-8. From Java 7, when using UTF_8 it sets this flag and uses normal UTF-8.)
flags |= ZipFile.GPBF_UTF8_FLAG;
writeLong(out, LOCSIG); // Entry header
writeShort(out, ZIP_VERSION_2_0); // Minimum version needed to extract.
@@ -331,7 +337,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
writeLong(out, 0);
writeLong(out, 0);
}
- writeShort(out, nameLength);
+ writeShort(out, nameBytes.length);
if (currentEntry.extra != null) {
writeShort(out, currentEntry.extra.length);
} else {
@@ -345,18 +351,16 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
/**
* Sets the comment associated with the file being written. See {@link ZipFile#getComment}.
- * @throws IllegalArgumentException if the comment is >= 64 Ki UTF-8 bytes.
+ * @throws IllegalArgumentException if the comment is >= 64 Ki encoded bytes.
*/
public void setComment(String comment) {
if (comment == null) {
- this.commentBytes = null;
+ this.commentBytes = EmptyArray.BYTE;
return;
}
byte[] newCommentBytes = comment.getBytes(StandardCharsets.UTF_8);
- if (newCommentBytes.length > 0xffff) {
- throw new IllegalArgumentException("Comment too long: " + newCommentBytes.length + " bytes");
- }
+ checkSizeIsWithinShort("Comment", newCommentBytes);
this.commentBytes = newCommentBytes;
}
@@ -400,7 +404,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
/**
* Writes data for the current entry to the underlying stream.
*
- * @exception IOException
+ * @throws IOException
* If an error occurs writing to the stream
*/
@Override
@@ -423,4 +427,11 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
throw new IOException("Stream is closed");
}
}
+
+ private void checkSizeIsWithinShort(String property, byte[] bytes) {
+ if (bytes.length > 0xffff) {
+ throw new IllegalArgumentException(property + " too long in UTF-8:" + bytes.length +
+ " bytes");
+ }
+ }
}
diff --git a/luni/src/main/java/javax/crypto/Cipher.java b/luni/src/main/java/javax/crypto/Cipher.java
index ba40b86..2e3b341 100644
--- a/luni/src/main/java/javax/crypto/Cipher.java
+++ b/luni/src/main/java/javax/crypto/Cipher.java
@@ -26,11 +26,15 @@ import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
+import java.security.Provider.Service;
+import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.Locale;
import java.util.Set;
import org.apache.harmony.crypto.internal.NullCipherSpi;
import org.apache.harmony.security.fortress.Engine;
@@ -54,7 +58,7 @@ import org.apache.harmony.security.fortress.Engine;
* <ul>
* {@code Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");}
* </ul>
- * When a block cipher is requested in in stream cipher mode, the number of bits
+ * When a block cipher is requested in stream cipher mode, the number of bits
* to be processed at a time can be optionally specified by appending it to the
* mode name. e.g. <i>"AES/CFB8/NoPadding"</i>. If no number is specified, a
* provider specific default value is used.
@@ -98,6 +102,11 @@ public class Cipher {
private int mode;
+ /** Items that need to be set on the Cipher instance. */
+ private enum NeedToSet {
+ NONE, MODE, PADDING, BOTH,
+ };
+
/**
* The service name.
*/
@@ -108,20 +117,46 @@ public class Cipher {
*/
private static final Engine ENGINE = new Engine(SERVICE);
+ /** The attribute used for supported paddings. */
+ private static final String ATTRIBUTE_PADDINGS = "SupportedPaddings";
+
+ /** The attribute used for supported modes. */
+ private static final String ATTRIBUTE_MODES = "SupportedModes";
+
/**
* The provider.
*/
private Provider provider;
/**
+ * The provider specified when instance created.
+ */
+ private final Provider specifiedProvider;
+
+ /**
* The SPI implementation.
*/
private CipherSpi spiImpl;
/**
+ * The SPI implementation.
+ */
+ private final CipherSpi specifiedSpi;
+
+ /**
* The transformation.
*/
- private String transformation;
+ private final String transformation;
+
+ /**
+ * The transformation split into parts.
+ */
+ private final String[] transformParts;
+
+ /**
+ * Lock held while the SPI is initializing.
+ */
+ private final Object initLock = new Object();
private static SecureRandom secureRandom;
@@ -138,19 +173,27 @@ public class Cipher {
* if either cipherSpi is {@code null} or provider is {@code
* null} and {@code cipherSpi} is a {@code NullCipherSpi}.
*/
- protected Cipher(CipherSpi cipherSpi, Provider provider,
- String transformation) {
+ protected Cipher(CipherSpi cipherSpi, Provider provider, String transformation) {
if (cipherSpi == null) {
throw new NullPointerException("cipherSpi == null");
}
if (!(cipherSpi instanceof NullCipherSpi) && provider == null) {
throw new NullPointerException("provider == null");
}
- this.provider = provider;
+ this.specifiedProvider = provider;
+ this.specifiedSpi = cipherSpi;
this.transformation = transformation;
- this.spiImpl = cipherSpi;
+ this.transformParts = null;
}
+ private Cipher(String transformation, String[] transformParts, Provider provider) {
+ this.transformation = transformation;
+ this.transformParts = transformParts;
+ this.specifiedProvider = provider;
+ this.specifiedSpi = null;
+ }
+
+
/**
* Creates a new Cipher for the specified transformation. The installed
* providers are searched in order for an implementation of the specified
@@ -211,7 +254,8 @@ public class Cipher {
}
/**
- * Creates a new cipher for the specified transformation.
+ * Creates a new cipher for the specified transformation. The
+ * {@code provider} supplied does not have to be registered.
*
* @param transformation
* the name of the transformation to create a cipher for.
@@ -234,8 +278,7 @@ public class Cipher {
if (provider == null) {
throw new IllegalArgumentException("provider == null");
}
- Cipher c = getCipher(transformation, provider);
- return c;
+ return getCipher(transformation, provider);
}
private static NoSuchAlgorithmException invalidTransformation(String transformation)
@@ -244,91 +287,31 @@ public class Cipher {
}
/**
- * Find appropriate Cipher according the specification rules
- *
- * @param transformation
- * @param provider
- * @return
- * @throws NoSuchAlgorithmException
- * @throws NoSuchPaddingException
+ * Create a Cipher instance but don't choose a CipherSpi until we have more
+ * information.
*/
- private static synchronized Cipher getCipher(String transformation, Provider provider)
+ private static Cipher getCipher(String transformation, Provider provider)
throws NoSuchAlgorithmException, NoSuchPaddingException {
-
if (transformation == null || transformation.isEmpty()) {
throw invalidTransformation(transformation);
}
- String[] transf = checkTransformation(transformation);
-
- boolean needSetPadding = false;
- boolean needSetMode = false;
- Object engineSpi = null;
- Provider engineProvider = provider;
- if (transf[1] == null && transf[2] == null) { // "algorithm"
+ String[] transformParts = checkTransformation(transformation);
+ if (tryCombinations(null, provider, transformParts) == null) {
if (provider == null) {
- Engine.SpiAndProvider sap = ENGINE.getInstance(transf[0], null);
- engineSpi = sap.spi;
- engineProvider = sap.provider;
+ throw new NoSuchAlgorithmException("No provider found for " + transformation);
} else {
- engineSpi = ENGINE.getInstance(transf[0], provider, null);
+ throw new NoSuchAlgorithmException("Provider " + provider.getName()
+ + " does not provide " + transformation);
}
- } else {
- String[] searchOrder = {
- transf[0] + "/" + transf[1] + "/" + transf[2], // "algorithm/mode/padding"
- transf[0] + "/" + transf[1], // "algorithm/mode"
- transf[0] + "//" + transf[2], // "algorithm//padding"
- transf[0] // "algorithm"
- };
- int i;
- for (i = 0; i < searchOrder.length; i++) {
- try {
- if (provider == null) {
- Engine.SpiAndProvider sap = ENGINE.getInstance(searchOrder[i], null);
- engineSpi = sap.spi;
- engineProvider = sap.provider;
- } else {
- engineSpi = ENGINE.getInstance(searchOrder[i], provider, null);
- }
- break;
- } catch (NoSuchAlgorithmException e) {
- if (i == searchOrder.length-1) {
- throw new NoSuchAlgorithmException(transformation, e);
- }
- }
- }
- switch (i) {
- case 1: // "algorithm/mode"
- needSetPadding = true;
- break;
- case 2: // "algorithm//padding"
- needSetMode = true;
- break;
- case 3: // "algorithm"
- needSetPadding = true;
- needSetMode = true;
- }
- }
- if (engineSpi == null || engineProvider == null) {
- throw new NoSuchAlgorithmException(transformation);
}
- if (!(engineSpi instanceof CipherSpi)) {
- throw new NoSuchAlgorithmException(engineSpi.getClass().getName());
- }
- CipherSpi cspi = (CipherSpi) engineSpi;
- Cipher c = new Cipher(cspi, engineProvider, transformation);
- if (needSetMode) {
- c.spiImpl.engineSetMode(transf[1]);
- }
- if (needSetPadding) {
- c.spiImpl.engineSetPadding(transf[2]);
- }
- return c;
+ return new Cipher(transformation, transformParts, provider);
}
- private static String[] checkTransformation(String transformation) throws NoSuchAlgorithmException {
+ private static String[] checkTransformation(String transformation)
+ throws NoSuchAlgorithmException {
// ignore an extra prefix / characters such as in
- // "/DES/CBC/PKCS5Paddin" http://b/3387688
+ // "/DES/CBC/PKCS5Padding" http://b/3387688
if (transformation.startsWith("/")) {
transformation = transformation.substring(1);
}
@@ -356,11 +339,163 @@ public class Cipher {
}
/**
+ * Makes sure a CipherSpi that matches this type is selected.
+ */
+ private CipherSpi getSpi(Key key) {
+ if (specifiedSpi != null) {
+ return specifiedSpi;
+ }
+
+ synchronized (initLock) {
+ if (spiImpl != null && key == null) {
+ return spiImpl;
+ }
+
+ final Engine.SpiAndProvider sap = tryCombinations(key, specifiedProvider,
+ transformParts);
+ if (sap == null) {
+ throw new ProviderException("No provider for " + transformation);
+ }
+
+ spiImpl = (CipherSpi) sap.spi;
+ provider = sap.provider;
+
+ return spiImpl;
+ }
+ }
+
+ /**
+ * Convenience call when the Key is not available.
+ */
+ private CipherSpi getSpi() {
+ return getSpi(null);
+ }
+
+ /**
+ * Try all combinations of mode strings:
+ *
+ * <pre>
+ * [cipher]/[mode]/[padding]
+ * [cipher]/[mode]
+ * [cipher]//[padding]
+ * [cipher]
+ * </pre>
+ */
+ private static Engine.SpiAndProvider tryCombinations(Key key, Provider provider,
+ String[] transformParts) {
+ Engine.SpiAndProvider sap = null;
+
+ if (transformParts[1] != null && transformParts[2] != null) {
+ sap = tryTransform(key, provider, transformParts[0] + "/" + transformParts[1] + "/"
+ + transformParts[2], transformParts, NeedToSet.NONE);
+ if (sap != null) {
+ return sap;
+ }
+ }
+
+ if (transformParts[1] != null) {
+ sap = tryTransform(key, provider, transformParts[0] + "/" + transformParts[1],
+ transformParts, NeedToSet.PADDING);
+ if (sap != null) {
+ return sap;
+ }
+ }
+
+ if (transformParts[2] != null) {
+ sap = tryTransform(key, provider, transformParts[0] + "//" + transformParts[2],
+ transformParts, NeedToSet.MODE);
+ if (sap != null) {
+ return sap;
+ }
+ }
+
+ return tryTransform(key, provider, transformParts[0], transformParts, NeedToSet.BOTH);
+ }
+
+ private static Engine.SpiAndProvider tryTransform(Key key, Provider provider, String transform,
+ String[] transformParts, NeedToSet type) {
+ if (provider != null) {
+ Provider.Service service = provider.getService(SERVICE, transform);
+ if (service == null) {
+ return null;
+ }
+ return tryTransformWithProvider(key, transformParts, type, service);
+ }
+ ArrayList<Provider.Service> services = ENGINE.getServices(transform);
+ if (services == null) {
+ return null;
+ }
+ for (Provider.Service service : services) {
+ Engine.SpiAndProvider sap = tryTransformWithProvider(key, transformParts, type, service);
+ if (sap != null) {
+ return sap;
+ }
+ }
+ return null;
+ }
+
+ private static Engine.SpiAndProvider tryTransformWithProvider(Key key, String[] transformParts,
+ NeedToSet type, Provider.Service service) {
+ try {
+ if (key != null && !service.supportsParameter(key)) {
+ return null;
+ }
+
+ /*
+ * Check to see if the Cipher even supports the attributes before
+ * trying to instantiate it.
+ */
+ if (!matchAttribute(service, ATTRIBUTE_MODES, transformParts[1])
+ || !matchAttribute(service, ATTRIBUTE_PADDINGS, transformParts[2])) {
+ return null;
+ }
+
+ Engine.SpiAndProvider sap = ENGINE.getInstance(service, null);
+ if (sap.spi == null || sap.provider == null) {
+ return null;
+ }
+ if (!(sap.spi instanceof CipherSpi)) {
+ return null;
+ }
+ CipherSpi spi = (CipherSpi) sap.spi;
+ if (((type == NeedToSet.MODE) || (type == NeedToSet.BOTH))
+ && (transformParts[1] != null)) {
+ spi.engineSetMode(transformParts[1]);
+ }
+ if (((type == NeedToSet.PADDING) || (type == NeedToSet.BOTH))
+ && (transformParts[2] != null)) {
+ spi.engineSetPadding(transformParts[2]);
+ }
+ return sap;
+ } catch (NoSuchAlgorithmException ignored) {
+ } catch (NoSuchPaddingException ignored) {
+ }
+ return null;
+ }
+
+ /**
+ * If the attribute listed exists, check that it matches the regular
+ * expression.
+ */
+ private static boolean matchAttribute(Service service, String attr, String value) {
+ if (value == null) {
+ return true;
+ }
+ final String pattern = service.getAttribute(attr);
+ if (pattern == null) {
+ return true;
+ }
+ final String valueUc = value.toUpperCase(Locale.US);
+ return valueUc.matches(pattern.toUpperCase(Locale.US));
+ }
+
+ /**
* Returns the provider of this cipher instance.
*
* @return the provider of this cipher instance.
*/
public final Provider getProvider() {
+ getSpi();
return provider;
}
@@ -382,7 +517,7 @@ public class Cipher {
* @return this ciphers block size.
*/
public final int getBlockSize() {
- return spiImpl.engineGetBlockSize();
+ return getSpi().engineGetBlockSize();
}
/**
@@ -399,7 +534,7 @@ public class Cipher {
if (mode == 0) {
throw new IllegalStateException("Cipher has not yet been initialized");
}
- return spiImpl.engineGetOutputSize(inputLen);
+ return getSpi().engineGetOutputSize(inputLen);
}
/**
@@ -408,7 +543,7 @@ public class Cipher {
* @return the <i>initialization vector</i> for this cipher instance.
*/
public final byte[] getIV() {
- return spiImpl.engineGetIV();
+ return getSpi().engineGetIV();
}
/**
@@ -423,7 +558,7 @@ public class Cipher {
* parameters.
*/
public final AlgorithmParameters getParameters() {
- return spiImpl.engineGetParameters();
+ return getSpi().engineGetParameters();
}
/**
@@ -442,6 +577,13 @@ public class Cipher {
}
+ private void checkMode(int mode) {
+ if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE && mode != UNWRAP_MODE
+ && mode != WRAP_MODE) {
+ throw new InvalidParameterException("Invalid mode: " + mode);
+ }
+ }
+
/**
* Initializes this cipher instance with the specified key.
* <p>
@@ -516,17 +658,10 @@ public class Cipher {
// FIXME InvalidKeyException
// if keysize exceeds the maximum allowable keysize
// (jurisdiction policy files)
- spiImpl.engineInit(opmode, key, random);
+ getSpi(key).engineInit(opmode, key, random);
mode = opmode;
}
- private void checkMode(int mode) {
- if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE
- && mode != UNWRAP_MODE && mode != WRAP_MODE) {
- throw new InvalidParameterException("Invalid mode: " + mode);
- }
- }
-
/**
* Initializes this cipher instance with the specified key and algorithm
* parameters.
@@ -613,7 +748,7 @@ public class Cipher {
// FIXME InvalidAlgorithmParameterException
// cryptographic strength exceed the legal limits
// (jurisdiction policy files)
- spiImpl.engineInit(opmode, key, params, random);
+ getSpi(key).engineInit(opmode, key, params, random);
mode = opmode;
}
@@ -704,7 +839,7 @@ public class Cipher {
// FIXME InvalidAlgorithmParameterException
// cryptographic strength exceed the legal limits
// (jurisdiction policy files)
- spiImpl.engineInit(opmode, key, params, random);
+ getSpi(key).engineInit(opmode, key, params, random);
mode = opmode;
}
@@ -828,7 +963,8 @@ public class Cipher {
// FIXME InvalidKeyException
// if keysize exceeds the maximum allowable keysize
// (jurisdiction policy files)
- spiImpl.engineInit(opmode, certificate.getPublicKey(), random);
+ final Key key = certificate.getPublicKey();
+ getSpi(key).engineInit(opmode, key, random);
mode = opmode;
}
@@ -856,7 +992,7 @@ public class Cipher {
if (input.length == 0) {
return null;
}
- return spiImpl.engineUpdate(input, 0, input.length);
+ return getSpi().engineUpdate(input, 0, input.length);
}
/**
@@ -890,7 +1026,7 @@ public class Cipher {
if (input.length == 0) {
return null;
}
- return spiImpl.engineUpdate(input, inputOffset, inputLen);
+ return getSpi().engineUpdate(input, inputOffset, inputLen);
}
private static void checkInputOffsetAndCount(int inputArrayLength,
@@ -986,7 +1122,7 @@ public class Cipher {
if (input.length == 0) {
return 0;
}
- return spiImpl.engineUpdate(input, inputOffset, inputLen, output,
+ return getSpi().engineUpdate(input, inputOffset, inputLen, output,
outputOffset);
}
@@ -1022,7 +1158,7 @@ public class Cipher {
if (input == output) {
throw new IllegalArgumentException("input == output");
}
- return spiImpl.engineUpdate(input, output);
+ return getSpi().engineUpdate(input, output);
}
/**
@@ -1053,7 +1189,7 @@ public class Cipher {
if (input.length == 0) {
return;
}
- spiImpl.engineUpdateAAD(input, 0, input.length);
+ getSpi().engineUpdateAAD(input, 0, input.length);
}
/**
@@ -1089,7 +1225,7 @@ public class Cipher {
if (input.length == 0) {
return;
}
- spiImpl.engineUpdateAAD(input, inputOffset, inputLen);
+ getSpi().engineUpdateAAD(input, inputOffset, inputLen);
}
/**
@@ -1115,7 +1251,7 @@ public class Cipher {
if (input == null) {
throw new IllegalArgumentException("input == null");
}
- spiImpl.engineUpdateAAD(input);
+ getSpi().engineUpdateAAD(input);
}
/**
@@ -1139,7 +1275,7 @@ public class Cipher {
if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE) {
throw new IllegalStateException();
}
- return spiImpl.engineDoFinal(null, 0, 0);
+ return getSpi().engineDoFinal(null, 0, 0);
}
/**
@@ -1175,7 +1311,7 @@ public class Cipher {
if (outputOffset < 0) {
throw new IllegalArgumentException("outputOffset < 0. outputOffset=" + outputOffset);
}
- return spiImpl.engineDoFinal(null, 0, 0, output, outputOffset);
+ return getSpi().engineDoFinal(null, 0, 0, output, outputOffset);
}
/**
@@ -1201,7 +1337,7 @@ public class Cipher {
if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE) {
throw new IllegalStateException();
}
- return spiImpl.engineDoFinal(input, 0, input.length);
+ return getSpi().engineDoFinal(input, 0, input.length);
}
/**
@@ -1236,7 +1372,7 @@ public class Cipher {
throw new IllegalStateException();
}
checkInputOffsetAndCount(input.length, inputOffset, inputLen);
- return spiImpl.engineDoFinal(input, inputOffset, inputLen);
+ return getSpi().engineDoFinal(input, inputOffset, inputLen);
}
/**
@@ -1314,7 +1450,7 @@ public class Cipher {
throw new IllegalStateException();
}
checkInputOffsetAndCount(input.length, inputOffset, inputLen);
- return spiImpl.engineDoFinal(input, inputOffset, inputLen, output,
+ return getSpi().engineDoFinal(input, inputOffset, inputLen, output,
outputOffset);
}
@@ -1354,7 +1490,7 @@ public class Cipher {
if (input == output) {
throw new IllegalArgumentException("input == output");
}
- return spiImpl.engineDoFinal(input, output);
+ return getSpi().engineDoFinal(input, output);
}
/**
@@ -1376,7 +1512,7 @@ public class Cipher {
if (mode != WRAP_MODE) {
throw new IllegalStateException();
}
- return spiImpl.engineWrap(key);
+ return getSpi().engineWrap(key);
}
/**
@@ -1406,7 +1542,7 @@ public class Cipher {
if (mode != UNWRAP_MODE) {
throw new IllegalStateException();
}
- return spiImpl.engineUnwrap(wrappedKey, wrappedKeyAlgorithm,
+ return getSpi().engineUnwrap(wrappedKey, wrappedKeyAlgorithm,
wrappedKeyType);
}
diff --git a/luni/src/main/java/javax/crypto/CipherInputStream.java b/luni/src/main/java/javax/crypto/CipherInputStream.java
index f2f59c1..3061655 100644
--- a/luni/src/main/java/javax/crypto/CipherInputStream.java
+++ b/luni/src/main/java/javax/crypto/CipherInputStream.java
@@ -17,6 +17,7 @@
package javax.crypto;
+import java.io.BufferedInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -34,11 +35,8 @@ import libcore.io.Streams;
* CipherInputStream} tries to read the data an decrypt them before returning.
*/
public class CipherInputStream extends FilterInputStream {
-
- private static final int I_BUFFER_SIZE = 20;
-
private final Cipher cipher;
- private final byte[] inputBuffer = new byte[I_BUFFER_SIZE];
+ private final byte[] inputBuffer;
private byte[] outputBuffer;
private int outputIndex; // index of the first byte to return from outputBuffer
private int outputLength; // count of the bytes to return from outputBuffer
@@ -60,6 +58,11 @@ public class CipherInputStream extends FilterInputStream {
public CipherInputStream(InputStream is, Cipher c) {
super(is);
this.cipher = c;
+ int blockSize = Math.max(c.getBlockSize(), 1);
+ int bufferSize = Math.max(blockSize,
+ BufferedInputStream.DEFAULT_BUFFER_SIZE / blockSize * blockSize);
+ inputBuffer = new byte[bufferSize];
+ outputBuffer = new byte[bufferSize + ((blockSize > 1) ? 2 * blockSize : 0)];
}
/**
@@ -76,19 +79,13 @@ public class CipherInputStream extends FilterInputStream {
}
/**
- * Reads the next byte from this cipher input stream.
- *
- * @return the next byte, or {@code -1} if the end of the stream is reached.
- * @throws IOException
- * if an error occurs.
+ * Attempts to fill the input buffer and process some data through the
+ * cipher. Returns {@code true} if output from the cipher is available to
+ * use.
*/
- @Override
- public int read() throws IOException {
+ private boolean fillBuffer() throws IOException {
if (finished) {
- return (outputIndex == outputLength) ? -1 : outputBuffer[outputIndex++] & 0xFF;
- }
- if (outputIndex < outputLength) {
- return outputBuffer[outputIndex++] & 0xFF;
+ return false;
}
outputIndex = 0;
outputLength = 0;
@@ -107,7 +104,7 @@ public class CipherInputStream extends FilterInputStream {
throw new IOException("Error while finalizing cipher", e);
}
finished = true;
- break;
+ return outputLength != 0;
}
try {
outputLength = cipher.update(inputBuffer, 0, byteCount, outputBuffer, 0);
@@ -115,7 +112,25 @@ public class CipherInputStream extends FilterInputStream {
throw new AssertionError(e); // should not happen since we sized with getOutputSize
}
}
- return read();
+ return true;
+ }
+
+ /**
+ * Reads the next byte from this cipher input stream.
+ *
+ * @return the next byte, or {@code -1} if the end of the stream is reached.
+ * @throws IOException
+ * if an error occurs.
+ */
+ @Override
+ public int read() throws IOException {
+ if (in == null) {
+ throw new NullPointerException("in == null");
+ }
+ if (outputIndex == outputLength && !fillBuffer()) {
+ return -1;
+ }
+ return outputBuffer[outputIndex++] & 0xFF;
}
/**
@@ -137,18 +152,18 @@ public class CipherInputStream extends FilterInputStream {
if (in == null) {
throw new NullPointerException("in == null");
}
-
- int i;
- for (i = 0; i < len; ++i) {
- int b = read();
- if (b == -1) {
- return (i == 0) ? -1 : i;
- }
- if (buf != null) {
- buf[off+i] = (byte) b;
- }
+ if (outputIndex == outputLength && !fillBuffer()) {
+ return -1;
+ }
+ int available = outputLength - outputIndex;
+ if (available < len) {
+ len = available;
+ }
+ if (buf != null) {
+ System.arraycopy(outputBuffer, outputIndex, buf, off, len);
}
- return i;
+ outputIndex += len;
+ return len;
}
@Override
@@ -158,7 +173,7 @@ public class CipherInputStream extends FilterInputStream {
@Override
public int available() throws IOException {
- return 0;
+ return outputLength - outputIndex;
}
/**
diff --git a/luni/src/main/java/javax/crypto/ExemptionMechanism.java b/luni/src/main/java/javax/crypto/ExemptionMechanism.java
index 8745b78..c2d42e6 100644
--- a/luni/src/main/java/javax/crypto/ExemptionMechanism.java
+++ b/luni/src/main/java/javax/crypto/ExemptionMechanism.java
@@ -142,6 +142,7 @@ public class ExemptionMechanism {
/**
* Returns a new {@code ExemptionMechanism} instance that provides the
* specified exemption mechanism algorithm from the specified provider.
+ * The {@code provider} supplied does not have to be registered.
*
* @param algorithm
* the name of the requested exemption mechanism.
diff --git a/luni/src/main/java/javax/crypto/KeyAgreement.java b/luni/src/main/java/javax/crypto/KeyAgreement.java
index 51b4cd1..abcfd0e 100644
--- a/luni/src/main/java/javax/crypto/KeyAgreement.java
+++ b/luni/src/main/java/javax/crypto/KeyAgreement.java
@@ -23,9 +23,11 @@ import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
+import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
import org.apache.harmony.security.fortress.Engine;
/**
@@ -35,22 +37,33 @@ import org.apache.harmony.security.fortress.Engine;
*/
public class KeyAgreement {
+ // The service name.
+ private static final String SERVICE = "KeyAgreement";
+
// Used to access common engine functionality
- private static final Engine ENGINE = new Engine("KeyAgreement");
+ private static final Engine ENGINE = new Engine(SERVICE);
// Store SecureRandom
private static final SecureRandom RANDOM = new SecureRandom();
// Store used provider
- private final Provider provider;
+ private Provider provider;
+
+ // Provider that was requested during creation.
+ private final Provider specifiedProvider;
// Store used spi implementation
- private final KeyAgreementSpi spiImpl;
+ private KeyAgreementSpi spiImpl;
// Store used algorithm name
private final String algorithm;
/**
+ * Lock held while the SPI is initializing.
+ */
+ private final Object initLock = new Object();
+
+ /**
* Creates a new {@code KeyAgreement} instance.
*
* @param keyAgreeSpi
@@ -62,9 +75,9 @@ public class KeyAgreement {
*/
protected KeyAgreement(KeyAgreementSpi keyAgreeSpi, Provider provider,
String algorithm) {
- this.provider = provider;
- this.algorithm = algorithm;
this.spiImpl = keyAgreeSpi;
+ this.specifiedProvider = provider;
+ this.algorithm = algorithm;
}
/**
@@ -82,6 +95,7 @@ public class KeyAgreement {
* @return the provider for this {@code KeyAgreement} instance.
*/
public final Provider getProvider() {
+ getSpi();
return provider;
}
@@ -96,13 +110,8 @@ public class KeyAgreement {
* @throws NullPointerException
* if the specified algorithm is {@code null}.
*/
- public static final KeyAgreement getInstance(String algorithm)
- throws NoSuchAlgorithmException {
- if (algorithm == null) {
- throw new NullPointerException("algorithm == null");
- }
- Engine.SpiAndProvider sap = ENGINE.getInstance(algorithm, null);
- return new KeyAgreement((KeyAgreementSpi) sap.spi, sap.provider, algorithm);
+ public static final KeyAgreement getInstance(String algorithm) throws NoSuchAlgorithmException {
+ return getKeyAgreement(algorithm, null);
}
/**
@@ -124,9 +133,8 @@ public class KeyAgreement {
* @throws IllegalArgumentException
* if the specified provider name is {@code null} or empty.
*/
- public static final KeyAgreement getInstance(String algorithm,
- String provider) throws NoSuchAlgorithmException,
- NoSuchProviderException {
+ public static final KeyAgreement getInstance(String algorithm, String provider)
+ throws NoSuchAlgorithmException, NoSuchProviderException {
if (provider == null || provider.isEmpty()) {
throw new IllegalArgumentException("Provider is null or empty");
}
@@ -134,12 +142,13 @@ public class KeyAgreement {
if (impProvider == null) {
throw new NoSuchProviderException(provider);
}
- return getInstance(algorithm, impProvider);
+ return getKeyAgreement(algorithm, impProvider);
}
/**
* Create a new {@code KeyAgreement} for the specified algorithm from the
- * specified provider.
+ * specified provider. The {@code provider} supplied does not have to be
+ * registered.
*
* @param algorithm
* the name of the key agreement algorithm to create.
@@ -155,29 +164,108 @@ public class KeyAgreement {
* @throws NullPointerException
* if the specified algorithm name is {@code null}.
*/
- public static final KeyAgreement getInstance(String algorithm,
- Provider provider) throws NoSuchAlgorithmException {
+ public static final KeyAgreement getInstance(String algorithm, Provider provider)
+ throws NoSuchAlgorithmException {
if (provider == null) {
throw new IllegalArgumentException("provider == null");
}
+ return getKeyAgreement(algorithm, provider);
+ }
+
+ private static KeyAgreement getKeyAgreement(String algorithm, Provider provider)
+ throws NoSuchAlgorithmException {
if (algorithm == null) {
throw new NullPointerException("algorithm == null");
}
- Object spi = ENGINE.getInstance(algorithm, provider, null);
- return new KeyAgreement((KeyAgreementSpi) spi, provider, algorithm);
+
+ if (tryAlgorithm(null, provider, algorithm) == null) {
+ if (provider == null) {
+ throw new NoSuchAlgorithmException("No provider found for " + algorithm);
+ } else {
+ throw new NoSuchAlgorithmException("Provider " + provider.getName()
+ + " does not provide " + algorithm);
+ }
+ }
+ return new KeyAgreement(null, provider, algorithm);
+ }
+
+ private static Engine.SpiAndProvider tryAlgorithm(Key key, Provider provider, String algorithm) {
+ if (provider != null) {
+ Provider.Service service = provider.getService(SERVICE, algorithm);
+ if (service == null) {
+ return null;
+ }
+ return tryAlgorithmWithProvider(key, service);
+ }
+ ArrayList<Provider.Service> services = ENGINE.getServices(algorithm);
+ if (services == null) {
+ return null;
+ }
+ for (Provider.Service service : services) {
+ Engine.SpiAndProvider sap = tryAlgorithmWithProvider(key, service);
+ if (sap != null) {
+ return sap;
+ }
+ }
+ return null;
+ }
+
+ private static Engine.SpiAndProvider tryAlgorithmWithProvider(Key key, Provider.Service service) {
+ try {
+ if (key != null && !service.supportsParameter(key)) {
+ return null;
+ }
+
+ Engine.SpiAndProvider sap = ENGINE.getInstance(service, null);
+ if (sap.spi == null || sap.provider == null) {
+ return null;
+ }
+ if (!(sap.spi instanceof KeyAgreementSpi)) {
+ return null;
+ }
+ return sap;
+ } catch (NoSuchAlgorithmException ignored) {
+ }
+ return null;
+ }
+
+ /**
+ * Makes sure a KeyAgreementSpi that matches this type is selected.
+ */
+ private KeyAgreementSpi getSpi(Key key) {
+ synchronized (initLock) {
+ if (spiImpl != null && key == null) {
+ return spiImpl;
+ }
+
+ final Engine.SpiAndProvider sap = tryAlgorithm(key, specifiedProvider, algorithm);
+ if (sap == null) {
+ throw new ProviderException("No provider for " + getAlgorithm());
+ }
+
+ spiImpl = (KeyAgreementSpi) sap.spi;
+ provider = sap.provider;
+
+ return spiImpl;
+ }
+ }
+
+ /**
+ * Convenience call when the Key is not available.
+ */
+ private KeyAgreementSpi getSpi() {
+ return getSpi(null);
}
/**
* Initializes this {@code KeyAgreement} with the specified key.
*
- * @param key
- * the key to initialize this key agreement.
- * @throws InvalidKeyException
- * if the specified key cannot be used to initialize this key
- * agreement.
+ * @param key the key to initialize this key agreement.
+ * @throws InvalidKeyException if the specified key cannot be used to
+ * initialize this key agreement.
*/
public final void init(Key key) throws InvalidKeyException {
- spiImpl.engineInit(key, RANDOM);//new SecureRandom());
+ getSpi(key).engineInit(key, RANDOM);//new SecureRandom());
}
/**
@@ -194,7 +282,7 @@ public class KeyAgreement {
*/
public final void init(Key key, SecureRandom random)
throws InvalidKeyException {
- spiImpl.engineInit(key, random);
+ getSpi(key).engineInit(key, random);
}
/**
@@ -214,7 +302,7 @@ public class KeyAgreement {
*/
public final void init(Key key, AlgorithmParameterSpec params)
throws InvalidKeyException, InvalidAlgorithmParameterException {
- spiImpl.engineInit(key, params, RANDOM);//new SecureRandom());
+ getSpi(key).engineInit(key, params, RANDOM);//new SecureRandom());
}
/**
@@ -237,7 +325,7 @@ public class KeyAgreement {
public final void init(Key key, AlgorithmParameterSpec params,
SecureRandom random) throws InvalidKeyException,
InvalidAlgorithmParameterException {
- spiImpl.engineInit(key, params, random);
+ getSpi(key).engineInit(key, params, random);
}
/**
@@ -259,7 +347,7 @@ public class KeyAgreement {
*/
public final Key doPhase(Key key, boolean lastPhase)
throws InvalidKeyException, IllegalStateException {
- return spiImpl.engineDoPhase(key, lastPhase);
+ return getSpi().engineDoPhase(key, lastPhase);
}
/**
@@ -270,7 +358,7 @@ public class KeyAgreement {
* if this key agreement is not complete.
*/
public final byte[] generateSecret() throws IllegalStateException {
- return spiImpl.engineGenerateSecret();
+ return getSpi().engineGenerateSecret();
}
/**
@@ -289,7 +377,7 @@ public class KeyAgreement {
*/
public final int generateSecret(byte[] sharedSecret, int offset)
throws IllegalStateException, ShortBufferException {
- return spiImpl.engineGenerateSecret(sharedSecret, offset);
+ return getSpi().engineGenerateSecret(sharedSecret, offset);
}
/**
@@ -311,7 +399,7 @@ public class KeyAgreement {
public final SecretKey generateSecret(String algorithm)
throws IllegalStateException, NoSuchAlgorithmException,
InvalidKeyException {
- return spiImpl.engineGenerateSecret(algorithm);
+ return getSpi().engineGenerateSecret(algorithm);
}
}
diff --git a/luni/src/main/java/javax/crypto/KeyGenerator.java b/luni/src/main/java/javax/crypto/KeyGenerator.java
index 606998a..fc409da 100644
--- a/luni/src/main/java/javax/crypto/KeyGenerator.java
+++ b/luni/src/main/java/javax/crypto/KeyGenerator.java
@@ -137,7 +137,8 @@ public class KeyGenerator {
/**
* Creates a new {@code KeyGenerator} instance that provides the specified
- * key algorithm from the specified provider.
+ * key algorithm from the specified provider. The {@code provider}
+ * supplied does not have to be registered.
*
* @param algorithm
* the name of the requested key algorithm.
diff --git a/luni/src/main/java/javax/crypto/Mac.java b/luni/src/main/java/javax/crypto/Mac.java
index c208456..5a73dc5 100644
--- a/luni/src/main/java/javax/crypto/Mac.java
+++ b/luni/src/main/java/javax/crypto/Mac.java
@@ -24,8 +24,10 @@ import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
+import java.security.ProviderException;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
import org.apache.harmony.security.fortress.Engine;
@@ -35,18 +37,29 @@ import org.apache.harmony.security.fortress.Engine;
*/
public class Mac implements Cloneable {
+ // The service name.
+ private static final String SERVICE = "Mac";
+
//Used to access common engine functionality
- private static final Engine ENGINE = new Engine("Mac");
+ private static final Engine ENGINE = new Engine(SERVICE);
// Store used provider
- private final Provider provider;
+ private Provider provider;
+
+ // Provider that was requested during creation.
+ private final Provider specifiedProvider;
// Store used spi implementation
- private final MacSpi spiImpl;
+ private MacSpi spiImpl;
// Store used algorithm name
private final String algorithm;
+ /**
+ * Lock held while the SPI is initializing.
+ */
+ private final Object initLock = new Object();
+
// Store Mac state (initialized or not initialized)
private boolean isInitMac;
@@ -61,7 +74,7 @@ public class Mac implements Cloneable {
* the name of the MAC algorithm.
*/
protected Mac(MacSpi macSpi, Provider provider, String algorithm) {
- this.provider = provider;
+ this.specifiedProvider = provider;
this.algorithm = algorithm;
this.spiImpl = macSpi;
this.isInitMac = false;
@@ -82,6 +95,7 @@ public class Mac implements Cloneable {
* @return the provider of this {@code Mac} instance.
*/
public final Provider getProvider() {
+ getSpi();
return provider;
}
@@ -100,11 +114,7 @@ public class Mac implements Cloneable {
*/
public static final Mac getInstance(String algorithm)
throws NoSuchAlgorithmException {
- if (algorithm == null) {
- throw new NullPointerException("algorithm == null");
- }
- Engine.SpiAndProvider sap = ENGINE.getInstance(algorithm, null);
- return new Mac((MacSpi) sap.spi, sap.provider, algorithm);
+ return getMac(algorithm, null);
}
/**
@@ -136,12 +146,13 @@ public class Mac implements Cloneable {
if (impProvider == null) {
throw new NoSuchProviderException(provider);
}
- return getInstance(algorithm, impProvider);
+ return getMac(algorithm, impProvider);
}
/**
* Creates a new {@code Mac} instance that provides the specified MAC
- * algorithm from the specified provider.
+ * algorithm from the specified provider. The {@code provider} supplied
+ * does not have to be registered.
*
* @param algorithm
* the name of the requested MAC algorithm.
@@ -162,11 +173,102 @@ public class Mac implements Cloneable {
if (provider == null) {
throw new IllegalArgumentException("provider == null");
}
+ return getMac(algorithm, provider);
+ }
+
+ private static Mac getMac(String algorithm, Provider provider)
+ throws NoSuchAlgorithmException {
if (algorithm == null) {
throw new NullPointerException("algorithm == null");
}
- Object spi = ENGINE.getInstance(algorithm, provider, null);
- return new Mac((MacSpi) spi, provider, algorithm);
+
+ if (tryAlgorithm(null, provider, algorithm) == null) {
+ if (provider == null) {
+ throw new NoSuchAlgorithmException("No provider found for " + algorithm);
+ } else {
+ throw new NoSuchAlgorithmException("Provider " + provider.getName()
+ + " does not provide " + algorithm);
+ }
+ }
+ return new Mac(null, provider, algorithm);
+ }
+
+ private static Engine.SpiAndProvider tryAlgorithm(Key key, Provider provider, String algorithm) {
+ if (provider != null) {
+ Provider.Service service = provider.getService(SERVICE, algorithm);
+ if (service == null) {
+ return null;
+ }
+ return tryAlgorithmWithProvider(key, service);
+ }
+ ArrayList<Provider.Service> services = ENGINE.getServices(algorithm);
+ if (services == null) {
+ return null;
+ }
+ for (Provider.Service service : services) {
+ Engine.SpiAndProvider sap = tryAlgorithmWithProvider(key, service);
+ if (sap != null) {
+ return sap;
+ }
+ }
+ return null;
+ }
+
+ private static Engine.SpiAndProvider tryAlgorithmWithProvider(Key key, Provider.Service service) {
+ try {
+ if (key != null && !service.supportsParameter(key)) {
+ return null;
+ }
+
+ Engine.SpiAndProvider sap = ENGINE.getInstance(service, null);
+ if (sap.spi == null || sap.provider == null) {
+ return null;
+ }
+ if (!(sap.spi instanceof MacSpi)) {
+ return null;
+ }
+ return sap;
+ } catch (NoSuchAlgorithmException ignored) {
+ }
+ return null;
+ }
+
+ /**
+ * Makes sure a MacSpi that matches this type is selected.
+ */
+ private MacSpi getSpi(Key key) {
+ synchronized (initLock) {
+ if (spiImpl != null && provider != null && key == null) {
+ return spiImpl;
+ }
+
+ if (algorithm == null) {
+ return null;
+ }
+
+ final Engine.SpiAndProvider sap = tryAlgorithm(key, specifiedProvider, algorithm);
+ if (sap == null) {
+ throw new ProviderException("No provider for " + getAlgorithm());
+ }
+
+ /*
+ * Set our Spi if we've never been initialized or if we have the Spi
+ * specified and have a null provider.
+ */
+ if (spiImpl == null || provider != null) {
+ spiImpl = (MacSpi) sap.spi;
+ }
+ provider = sap.provider;
+
+ return spiImpl;
+ }
+ }
+
+ /**
+ * Convenience call when the Key is not available.
+ */
+ private MacSpi getSpi() {
+ return getSpi(null);
}
/**
@@ -175,7 +277,7 @@ public class Mac implements Cloneable {
* @return the length of this MAC (in bytes).
*/
public final int getMacLength() {
- return spiImpl.engineGetMacLength();
+ return getSpi().engineGetMacLength();
}
/**
@@ -198,7 +300,7 @@ public class Mac implements Cloneable {
if (key == null) {
throw new InvalidKeyException("key == null");
}
- spiImpl.engineInit(key, params);
+ getSpi(key).engineInit(key, params);
isInitMac = true;
}
@@ -219,7 +321,7 @@ public class Mac implements Cloneable {
throw new InvalidKeyException("key == null");
}
try {
- spiImpl.engineInit(key, null);
+ getSpi(key).engineInit(key, null);
isInitMac = true;
} catch (InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
@@ -238,7 +340,7 @@ public class Mac implements Cloneable {
if (!isInitMac) {
throw new IllegalStateException();
}
- spiImpl.engineUpdate(input);
+ getSpi().engineUpdate(input);
}
/**
@@ -269,7 +371,7 @@ public class Mac implements Cloneable {
+ " input.length=" + input.length
+ " offset=" + offset + ", len=" + len);
}
- spiImpl.engineUpdate(input, offset, len);
+ getSpi().engineUpdate(input, offset, len);
}
/**
@@ -285,7 +387,7 @@ public class Mac implements Cloneable {
throw new IllegalStateException();
}
if (input != null) {
- spiImpl.engineUpdate(input, 0, input.length);
+ getSpi().engineUpdate(input, 0, input.length);
}
}
@@ -304,7 +406,7 @@ public class Mac implements Cloneable {
throw new IllegalStateException();
}
if (input != null) {
- spiImpl.engineUpdate(input);
+ getSpi().engineUpdate(input);
} else {
throw new IllegalArgumentException("input == null");
}
@@ -326,7 +428,7 @@ public class Mac implements Cloneable {
if (!isInitMac) {
throw new IllegalStateException();
}
- return spiImpl.engineDoFinal();
+ return getSpi().engineDoFinal();
}
/**
@@ -361,11 +463,12 @@ public class Mac implements Cloneable {
if ((outOffset < 0) || (outOffset >= output.length)) {
throw new ShortBufferException("Incorrect outOffset: " + outOffset);
}
- int t = spiImpl.engineGetMacLength();
+ MacSpi spi = getSpi();
+ int t = spi.engineGetMacLength();
if (t > (output.length - outOffset)) {
throw new ShortBufferException("Output buffer is short. Needed " + t + " bytes.");
}
- byte[] result = spiImpl.engineDoFinal();
+ byte[] result = spi.engineDoFinal();
System.arraycopy(result, 0, output, outOffset, result.length);
}
@@ -389,10 +492,11 @@ public class Mac implements Cloneable {
if (!isInitMac) {
throw new IllegalStateException();
}
+ MacSpi spi = getSpi();
if (input != null) {
- spiImpl.engineUpdate(input, 0, input.length);
+ spi.engineUpdate(input, 0, input.length);
}
- return spiImpl.engineDoFinal();
+ return spi.engineDoFinal();
}
/**
@@ -403,7 +507,7 @@ public class Mac implements Cloneable {
* initialized with different parameters.
*/
public final void reset() {
- spiImpl.engineReset();
+ getSpi().engineReset();
}
/**
@@ -415,7 +519,11 @@ public class Mac implements Cloneable {
*/
@Override
public final Object clone() throws CloneNotSupportedException {
- MacSpi newSpiImpl = (MacSpi)spiImpl.clone();
+ MacSpi newSpiImpl = null;
+ final MacSpi spi = getSpi();
+ if (spi != null) {
+ newSpiImpl = (MacSpi) spi.clone();
+ }
Mac mac = new Mac(newSpiImpl, this.provider, this.algorithm);
mac.isInitMac = this.isInitMac;
return mac;
diff --git a/luni/src/main/java/javax/crypto/SealedObject.java b/luni/src/main/java/javax/crypto/SealedObject.java
index cfb970b..4b91184 100644
--- a/luni/src/main/java/javax/crypto/SealedObject.java
+++ b/luni/src/main/java/javax/crypto/SealedObject.java
@@ -19,6 +19,7 @@ package javax.crypto;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
@@ -29,6 +30,7 @@ import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
+import libcore.io.IoUtils;
/**
* A {@code SealedObject} is a wrapper around a {@code serializable} object
@@ -57,14 +59,21 @@ public class SealedObject implements Serializable {
private String paramsAlg;
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
- // We do unshared reads here to ensure we have our own clones of the byte[]s.
- encodedParams = (byte[]) s.readUnshared();
- encryptedContent = (byte[]) s.readUnshared();
- // These are regular shared reads because the algorithms used by a given stream are
- // almost certain to the be same for each object, and String is immutable anyway,
- // so there's no security concern about sharing.
- sealAlg = (String) s.readObject();
- paramsAlg = (String) s.readObject();
+ // This implementation is based on the latest recommendations for safe deserialization at
+ // the time of writing. See the Serialization spec section A.6.
+ ObjectInputStream.GetField fields = s.readFields();
+
+ // The mutable byte arrays are cloned and the immutable strings are not.
+ this.encodedParams = getSafeCopy(fields, "encodedParams");
+ this.encryptedContent = getSafeCopy(fields, "encryptedContent");
+ this.paramsAlg = (String) fields.get("paramsAlg", null);
+ this.sealAlg = (String) fields.get("sealAlg", null);
+ }
+
+ private static byte[] getSafeCopy(ObjectInputStream.GetField fields, String fieldName)
+ throws IOException {
+ byte[] fieldValue = (byte[]) fields.get(fieldName, null);
+ return fieldValue != null ? fieldValue.clone() : null;
}
/**
@@ -87,13 +96,14 @@ public class SealedObject implements Serializable {
* if the cipher is {@code null}.
*/
public SealedObject(Serializable object, Cipher c)
- throws IOException, IllegalBlockSizeException {
+ throws IOException, IllegalBlockSizeException {
if (c == null) {
throw new NullPointerException("c == null");
}
+ ObjectOutputStream oos = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(bos);
+ oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
AlgorithmParameters ap = c.getParameters();
@@ -105,6 +115,8 @@ public class SealedObject implements Serializable {
// should be never thrown because the cipher
// should be initialized for encryption
throw new IOException(e.toString());
+ } finally {
+ IoUtils.closeQuietly(oos);
}
}
@@ -119,8 +131,10 @@ public class SealedObject implements Serializable {
if (so == null) {
throw new NullPointerException("so == null");
}
- this.encryptedContent = so.encryptedContent;
- this.encodedParams = so.encodedParams;
+ // For safety: clone the mutable arrays so that each object has its own independent copy of
+ // the data.
+ this.encryptedContent = so.encryptedContent != null ? so.encryptedContent.clone() : null;
+ this.encodedParams = so.encodedParams != null ? so.encodedParams.clone() : null;
this.sealAlg = so.sealAlg;
this.paramsAlg = so.paramsAlg;
}
@@ -158,18 +172,14 @@ public class SealedObject implements Serializable {
try {
Cipher cipher = Cipher.getInstance(sealAlg);
if ((paramsAlg != null) && (paramsAlg.length() != 0)) {
- AlgorithmParameters params =
- AlgorithmParameters.getInstance(paramsAlg);
+ AlgorithmParameters params = AlgorithmParameters.getInstance(paramsAlg);
params.init(encodedParams);
cipher.init(Cipher.DECRYPT_MODE, key, params);
} else {
cipher.init(Cipher.DECRYPT_MODE, key);
}
byte[] serialized = cipher.doFinal(encryptedContent);
- ObjectInputStream ois =
- new ObjectInputStream(
- new ByteArrayInputStream(serialized));
- return ois.readObject();
+ return readSerialized(serialized);
} catch (NoSuchPaddingException e) {
// should not be thrown because cipher text was made
// with existing padding
@@ -186,7 +196,7 @@ public class SealedObject implements Serializable {
// should not be thrown because the cipher text
// was correctly made
throw new NoSuchAlgorithmException(e.toString());
- } catch (IllegalStateException e) {
+ } catch (IllegalStateException e) {
// should never be thrown because cipher is initialized
throw new NoSuchAlgorithmException(e.toString());
}
@@ -217,10 +227,7 @@ public class SealedObject implements Serializable {
throw new NullPointerException("c == null");
}
byte[] serialized = c.doFinal(encryptedContent);
- ObjectInputStream ois =
- new ObjectInputStream(
- new ByteArrayInputStream(serialized));
- return ois.readObject();
+ return readSerialized(serialized);
}
/**
@@ -253,18 +260,14 @@ public class SealedObject implements Serializable {
try {
Cipher cipher = Cipher.getInstance(sealAlg, provider);
if ((paramsAlg != null) && (paramsAlg.length() != 0)) {
- AlgorithmParameters params =
- AlgorithmParameters.getInstance(paramsAlg);
+ AlgorithmParameters params = AlgorithmParameters.getInstance(paramsAlg);
params.init(encodedParams);
cipher.init(Cipher.DECRYPT_MODE, key, params);
} else {
cipher.init(Cipher.DECRYPT_MODE, key);
}
byte[] serialized = cipher.doFinal(encryptedContent);
- ObjectInputStream ois =
- new ObjectInputStream(
- new ByteArrayInputStream(serialized));
- return ois.readObject();
+ return readSerialized(serialized);
} catch (NoSuchPaddingException e) {
// should not be thrown because cipher text was made
// with existing padding
@@ -286,4 +289,15 @@ public class SealedObject implements Serializable {
throw new NoSuchAlgorithmException(e.toString());
}
}
+
+ private static Object readSerialized(byte[] serialized)
+ throws IOException, ClassNotFoundException {
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(new ByteArrayInputStream(serialized));
+ return ois.readObject();
+ } finally {
+ IoUtils.closeQuietly(ois);
+ }
+ }
}
diff --git a/luni/src/main/java/javax/crypto/SecretKeyFactory.java b/luni/src/main/java/javax/crypto/SecretKeyFactory.java
index 8ab3eb8..9298b8e 100644
--- a/luni/src/main/java/javax/crypto/SecretKeyFactory.java
+++ b/luni/src/main/java/javax/crypto/SecretKeyFactory.java
@@ -143,7 +143,8 @@ public class SecretKeyFactory {
/**
* Creates a new {@code SecretKeyFactory} instance for the specified key
- * algorithm from the specified provider.
+ * algorithm from the specified provider. The {@code provider} supplied
+ * does not have to be registered.
*
* @param algorithm
* the name of the key algorithm.
diff --git a/luni/src/main/java/javax/net/ssl/SSLContext.java b/luni/src/main/java/javax/net/ssl/SSLContext.java
index a59f301..efc1947 100644
--- a/luni/src/main/java/javax/net/ssl/SSLContext.java
+++ b/luni/src/main/java/javax/net/ssl/SSLContext.java
@@ -82,6 +82,46 @@ public class SSLContext {
/**
* Creates a new {@code SSLContext} instance for the specified protocol.
*
+ * <p>The following protocols are supported:
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Protocol</th>
+ * <th>API Levels</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>Default</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>SSL</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>SSLv3</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1.1</td>
+ * <td>16+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1.2</td>
+ * <td>16+</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
* @param protocol
* the requested protocol to create a context for.
* @return the created {@code SSLContext} instance.
@@ -103,6 +143,79 @@ public class SSLContext {
* Creates a new {@code SSLContext} instance for the specified protocol from
* the specified provider.
*
+ * <p>The following combinations are supported:
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Protocol</th>
+ * <th>Provider</th>
+ * <th>API Levels</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>Default</td>
+ * <td>AndroidOpenSSL</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>SSL</td>
+ * <td>AndroidOpenSSL</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>SSL</td>
+ * <td>HarmonyJSSE</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSLv3</td>
+ * <td>AndroidOpenSSL</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>SSLv3</td>
+ * <td>HarmonyJSSE</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS</td>
+ * <td>AndroidOpenSSL</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS</td>
+ * <td>HarmonyJSSE</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1</td>
+ * <td>AndroidOpenSSL</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1</td>
+ * <td>HarmonyJSSE</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1.1</td>
+ * <td>AndroidOpenSSL</td>
+ * <td>16+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1.2</td>
+ * <td>AndroidOpenSSL</td>
+ * <td>16+</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <p><strong>NOTE:</strong> The best practice is to rely on platform
+ * defaults rather than explicitly specify a provider.
+ * {@link #getDefault()} and {@link #getInstance(String)} are normally
+ * preferred over this method.
+ *
* @param protocol
* the requested protocol to create a context for.
* @param provider
@@ -201,16 +314,33 @@ public class SSLContext {
}
/**
- * Initializes this {@code SSLContext} instance. All of the arguments are
- * optional, and the security providers will be searched for the required
- * implementations of the needed algorithms.
+ * Initializes this {@code SSLContext} instance. Three aspects of the context can be configured
+ * during initialization:
+ * <ul>
+ * <li>Providers of key material for key exchange and peer authentication
+ * ({@link KeyManager} instances),</li>
+ * <li>Providers of trust decisions about peers ({@link TrustManager} instances),
+ * </li>
+ * <li>Provider of randomness ({@link SecureRandom} instance).</li>
+ * </ul>
+ *
+ * <p>For each type of {@code KeyManager} or {@code TrustManager} used by this context, only the
+ * first matching instance from {@code km} or {@code tm} will be used. For example, only the
+ * first instance of {@link X509TrustManager} from {@code tm} will be used.
+ *
+ * <p>For any parameter set to {@code null} defaults will be used. In that case, the installed
+ * security providers will be searched for the highest priority implementation of the required
+ * primitives. For {@code km} and {@code tm}, the highest priority implementation
+ * of {@link KeyManagerFactory} and {@link TrustManagerFactory} will be used to obtain the
+ * required types of {@code KeyManager} and {@code TrustManager}. For {@code sr}, the default
+ * {@code SecureRandom} implementation will be used.
*
* @param km
- * the key sources or {@code null}.
+ * the key sources or {@code null} for default.
* @param tm
- * the trust decision sources or {@code null}.
+ * the trust decision sources or {@code null} for default.
* @param sr
- * the randomness source or {@code null.}
+ * the randomness source or {@code null} for default.
* @throws KeyManagementException
* if initializing this instance fails.
*/
diff --git a/luni/src/main/java/javax/net/ssl/SSLEngine.java b/luni/src/main/java/javax/net/ssl/SSLEngine.java
index a6c9946..cbf02ac 100644
--- a/luni/src/main/java/javax/net/ssl/SSLEngine.java
+++ b/luni/src/main/java/javax/net/ssl/SSLEngine.java
@@ -24,6 +24,629 @@ import java.nio.ByteBuffer;
* protocols. It includes the setup, handshake, and encrypt/decrypt
* functionality needed to create a secure connection.
*
+ * <h3>Default configuration</h3>
+ * <p>{@code SSLEngine} instances obtained from default {@link SSLContext} are configured as
+ * follows:
+ *
+ * <h4>Protocols</h4>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Protocol</th>
+ * <th>Supported (API Levels)</th>
+ * <th>Enabled by default (API Levels)</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>SSLv3</td>
+ * <td>1+</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1</td>
+ * <td>1+</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1.1</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1.2</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <h4>Cipher suites</h4>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Cipher suite</th>
+ * <th>Supported (API Levels)</th>
+ * <th>Enabled by default (API Levels)</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DHE_DSS_WITH_DES_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DHE_RSA_WITH_DES_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DH_anon_EXPORT_WITH_RC4_40_MD5</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DH_anon_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DH_anon_WITH_DES_CBC_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DH_anon_WITH_RC4_128_MD5</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_EXPORT_WITH_RC4_40_MD5</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_DES_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_NULL_MD5</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_NULL_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_RC4_128_MD5</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_RC4_128_SHA</td>
+ * <td>9+</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_DES_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_DES_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_DSS_WITH_DES_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_RSA_WITH_DES_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_128_CBC_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_256_CBC_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_256_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_DES_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_NULL_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_RC4_128_SHA</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_NULL_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_RC4_128_SHA</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_NULL_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_RC4_128_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_128_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_256_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_NULL_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_RC4_128_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_anon_WITH_AES_128_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_anon_WITH_AES_256_CBC_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_anon_WITH_NULL_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_anon_WITH_RC4_128_SHA</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_EMPTY_RENEGOTIATION_INFO_SCSV</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_FALLBACK_SCSV</td>
+ * <td>21+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA</td>
+ * <td>21+</td>
+ * <td>21+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA</td>
+ * <td>21+</td>
+ * <td>21+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_NULL_WITH_NULL_NULL</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_PSK_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>21+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_PSK_WITH_AES_128_CBC_SHA</td>
+ * <td>21+</td>
+ * <td>21+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_PSK_WITH_AES_256_CBC_SHA</td>
+ * <td>21+</td>
+ * <td>21+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_PSK_WITH_RC4_128_SHA</td>
+ * <td>21+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_128_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_256_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_256_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_DES_CBC_SHA</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_NULL_MD5</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_NULL_SHA</td>
+ * <td>1-8</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_NULL_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <p><em>NOTE</em>: PSK cipher suites are enabled by default only if the {@code SSLContext} through
+ * which the engine was created has been initialized with a {@code PSKKeyManager}.
+ *
* @since 1.5
*/
public abstract class SSLEngine {
diff --git a/luni/src/main/java/javax/net/ssl/SSLEngineResult.java b/luni/src/main/java/javax/net/ssl/SSLEngineResult.java
index 8a98831..3360832 100644
--- a/luni/src/main/java/javax/net/ssl/SSLEngineResult.java
+++ b/luni/src/main/java/javax/net/ssl/SSLEngineResult.java
@@ -110,16 +110,16 @@ public class SSLEngineResult {
public SSLEngineResult(SSLEngineResult.Status status,
SSLEngineResult.HandshakeStatus handshakeStatus, int bytesConsumed, int bytesProduced) {
if (status == null) {
- throw new IllegalArgumentException("status is null");
+ throw new IllegalArgumentException("status == null");
}
if (handshakeStatus == null) {
- throw new IllegalArgumentException("handshakeStatus is null");
+ throw new IllegalArgumentException("handshakeStatus == null");
}
if (bytesConsumed < 0) {
- throw new IllegalArgumentException("bytesConsumed is negative");
+ throw new IllegalArgumentException("bytesConsumed < 0: " + bytesConsumed);
}
if (bytesProduced < 0) {
- throw new IllegalArgumentException("bytesProduced is negative");
+ throw new IllegalArgumentException("bytesProduced < 0: " + bytesProduced);
}
this.status = status;
this.handshakeStatus = handshakeStatus;
diff --git a/luni/src/main/java/javax/net/ssl/SSLParameters.java b/luni/src/main/java/javax/net/ssl/SSLParameters.java
index 6694ef2..054abe2 100644
--- a/luni/src/main/java/javax/net/ssl/SSLParameters.java
+++ b/luni/src/main/java/javax/net/ssl/SSLParameters.java
@@ -27,6 +27,7 @@ public class SSLParameters {
private String[] protocols;
private boolean needClientAuth;
private boolean wantClientAuth;
+ private String endpointIdentificationAlgorithm;
/**
* The default SSLParameters constructor. Cipher suites and
diff --git a/luni/src/main/java/javax/net/ssl/SSLServerSocketFactory.java b/luni/src/main/java/javax/net/ssl/SSLServerSocketFactory.java
index cce72cd..03b8828 100644
--- a/luni/src/main/java/javax/net/ssl/SSLServerSocketFactory.java
+++ b/luni/src/main/java/javax/net/ssl/SSLServerSocketFactory.java
@@ -20,6 +20,7 @@ package javax.net.ssl;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import javax.net.ServerSocketFactory;
+import org.apache.harmony.security.fortress.Services;
/**
* The factory for SSL server sockets.
@@ -32,6 +33,8 @@ public abstract class SSLServerSocketFactory extends ServerSocketFactory {
private static String defaultName;
+ private static int lastCacheVersion = -1;
+
/**
* Returns the default {@code SSLServerSocketFactory} instance. The default
* implementation is defined by the security property
@@ -40,6 +43,12 @@ public abstract class SSLServerSocketFactory extends ServerSocketFactory {
* @return the default {@code SSLServerSocketFactory} instance.
*/
public static synchronized ServerSocketFactory getDefault() {
+ int newCacheVersion = Services.getCacheVersion();
+ if (lastCacheVersion != newCacheVersion) {
+ defaultServerSocketFactory = null;
+ defaultName = null;
+ lastCacheVersion = newCacheVersion;
+ }
if (defaultServerSocketFactory != null) {
return defaultServerSocketFactory;
}
diff --git a/luni/src/main/java/javax/net/ssl/SSLSocket.java b/luni/src/main/java/javax/net/ssl/SSLSocket.java
index 5049f81..dc406e1 100644
--- a/luni/src/main/java/javax/net/ssl/SSLSocket.java
+++ b/luni/src/main/java/javax/net/ssl/SSLSocket.java
@@ -25,6 +25,715 @@ import java.net.UnknownHostException;
/**
* The extension of {@code Socket} providing secure protocols like SSL (Secure
* Sockets Layer) or TLS (Transport Layer Security).
+ *
+ * <h3>Default configuration</h3>
+ * <p>{@code SSLSocket} instances obtained from default {@link SSLSocketFactory},
+ * {@link SSLServerSocketFactory}, and {@link SSLContext} are configured as follows:
+ *
+ * <h4>Protocols</h4>
+ *
+ * <p>Client socket:
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Protocol</th>
+ * <th>Supported (API Levels)</th>
+ * <th>Enabled by default (API Levels)</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>SSLv3</td>
+ * <td>1+</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1</td>
+ * <td>1+</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1.1</td>
+ * <td>16+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1.2</td>
+ * <td>16+</td>
+ * <td>20+</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <p>Server socket:
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Protocol</th>
+ * <th>Supported (API Levels)</th>
+ * <th>Enabled by default (API Levels)</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>SSLv3</td>
+ * <td>1+</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1</td>
+ * <td>1+</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1.1</td>
+ * <td>16+</td>
+ * <td>16+</td>
+ * </tr>
+ * <tr>
+ * <td>TLSv1.2</td>
+ * <td>16+</td>
+ * <td>16+</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <h4>Cipher suites</h4>
+ *
+ * <p>Methods that operate with cipher suite names (for example,
+ * {@link #getSupportedCipherSuites() getSupportedCipherSuites},
+ * {@link #setEnabledCipherSuites(String[]) setEnabledCipherSuites}) have used
+ * standard names for cipher suites since API Level 9, as listed in the table
+ * below. Prior to API Level 9, non-standard (OpenSSL) names had been used (see
+ * the table following this table).
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Cipher suite</th>
+ * <th>Supported (API Levels)</th>
+ * <th>Enabled by default (API Levels)</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DHE_DSS_WITH_DES_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DHE_RSA_WITH_DES_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DH_anon_EXPORT_WITH_RC4_40_MD5</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DH_anon_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DH_anon_WITH_DES_CBC_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_DH_anon_WITH_RC4_128_MD5</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_EXPORT_WITH_RC4_40_MD5</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_DES_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_NULL_MD5</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_NULL_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_RC4_128_MD5</td>
+ * <td>9+</td>
+ * <td>9-19</td>
+ * </tr>
+ * <tr>
+ * <td>SSL_RSA_WITH_RC4_128_SHA</td>
+ * <td>9+</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>11+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_DSS_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>11+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DHE_RSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_128_CBC_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_256_CBC_SHA</td>
+ * <td>9+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_256_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_DH_anon_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_NULL_SHA</td>
+ * <td>11+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_ECDSA_WITH_RC4_128_SHA</td>
+ * <td>11+</td>
+ * <td>11+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA</td>
+ * <td>21+</td>
+ * <td>21+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA</td>
+ * <td>21+</td>
+ * <td>21+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_NULL_SHA</td>
+ * <td>11+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDHE_RSA_WITH_RC4_128_SHA</td>
+ * <td>11+</td>
+ * <td>11+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_NULL_SHA</td>
+ * <td>11+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_ECDSA_WITH_RC4_128_SHA</td>
+ * <td>11+</td>
+ * <td>11-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_128_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_256_CBC_SHA</td>
+ * <td>11+</td>
+ * <td>11-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_NULL_SHA</td>
+ * <td>11+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_RSA_WITH_RC4_128_SHA</td>
+ * <td>11+</td>
+ * <td>11-19</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>11+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_anon_WITH_AES_128_CBC_SHA</td>
+ * <td>11+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_anon_WITH_AES_256_CBC_SHA</td>
+ * <td>11+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_anon_WITH_NULL_SHA</td>
+ * <td>11+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_ECDH_anon_WITH_RC4_128_SHA</td>
+ * <td>11+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_EMPTY_RENEGOTIATION_INFO_SCSV</td>
+ * <td>11+</td>
+ * <td>11+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_FALLBACK_SCSV</td>
+ * <td>21+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_PSK_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>21+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_PSK_WITH_AES_128_CBC_SHA</td>
+ * <td>21+</td>
+ * <td>21+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_PSK_WITH_AES_256_CBC_SHA</td>
+ * <td>21+</td>
+ * <td>21+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_PSK_WITH_RC4_128_SHA</td>
+ * <td>21+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_128_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>9+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_128_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_128_GCM_SHA256</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_256_CBC_SHA</td>
+ * <td>9+</td>
+ * <td>11+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_256_CBC_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_AES_256_GCM_SHA384</td>
+ * <td>20+</td>
+ * <td>20+</td>
+ * </tr>
+ * <tr>
+ * <td>TLS_RSA_WITH_NULL_SHA256</td>
+ * <td>20+</td>
+ * <td></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <p><em>NOTE</em>: PSK cipher suites are enabled by default only if the {@code SSLContext} through
+ * which the socket was created has been initialized with a {@code PSKKeyManager}.
+ *
+ * <p>API Levels 1 to 8 use OpenSSL names for cipher suites. The table below
+ * lists these OpenSSL names and their corresponding standard names used in API
+ * Levels 9 and newer.
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>OpenSSL cipher suite</th>
+ * <th>Standard cipher suite</th>
+ * <th>Supported (API Levels)</th>
+ * <th>Enabled by default (API Levels)</th>
+ * </tr>
+ * </thead>
+ *
+ * <tbody>
+ * <tr>
+ * <td>AES128-SHA</td>
+ * <td>TLS_RSA_WITH_AES_128_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>AES256-SHA</td>
+ * <td>TLS_RSA_WITH_AES_256_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-8, 11+</td>
+ * </tr>
+ * <tr>
+ * <td>DES-CBC-MD5</td>
+ * <td>SSL_CK_DES_64_CBC_WITH_MD5</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>DES-CBC-SHA</td>
+ * <td>SSL_RSA_WITH_DES_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>DES-CBC3-MD5</td>
+ * <td>SSL_CK_DES_192_EDE3_CBC_WITH_MD5</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>DES-CBC3-SHA</td>
+ * <td>SSL_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>DHE-DSS-AES128-SHA</td>
+ * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>DHE-DSS-AES256-SHA</td>
+ * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-8, 11+</td>
+ * </tr>
+ * <tr>
+ * <td>DHE-RSA-AES128-SHA</td>
+ * <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1+</td>
+ * </tr>
+ * <tr>
+ * <td>DHE-RSA-AES256-SHA</td>
+ * <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-8, 11+</td>
+ * </tr>
+ * <tr>
+ * <td>EDH-DSS-DES-CBC-SHA</td>
+ * <td>SSL_DHE_DSS_WITH_DES_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>EDH-DSS-DES-CBC3-SHA</td>
+ * <td>SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>EDH-RSA-DES-CBC-SHA</td>
+ * <td>SSL_DHE_RSA_WITH_DES_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>EDH-RSA-DES-CBC3-SHA</td>
+ * <td>SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>EXP-DES-CBC-SHA</td>
+ * <td>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>EXP-EDH-DSS-DES-CBC-SHA</td>
+ * <td>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>EXP-EDH-RSA-DES-CBC-SHA</td>
+ * <td>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>EXP-RC2-CBC-MD5</td>
+ * <td>SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>EXP-RC4-MD5</td>
+ * <td>SSL_RSA_EXPORT_WITH_RC4_40_MD5</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>RC2-CBC-MD5</td>
+ * <td>SSL_CK_RC2_128_CBC_WITH_MD5</td>
+ * <td>1-8</td>
+ * <td>1-8</td>
+ * </tr>
+ * <tr>
+ * <td>RC4-MD5</td>
+ * <td>SSL_RSA_WITH_RC4_128_MD5</td>
+ * <td>1+</td>
+ * <td>1-19</td>
+ * </tr>
+ * <tr>
+ * <td>RC4-SHA</td>
+ * <td>SSL_RSA_WITH_RC4_128_SHA</td>
+ * <td>1+</td>
+ * <td>1+</td>
+ * </tr>
+ * </tbody>
+ * </table>
*/
public abstract class SSLSocket extends Socket {
@@ -188,13 +897,11 @@ public abstract class SSLSocket extends Socket {
public abstract SSLSession getSession();
/**
- * Registers the specified listener to receive notification on completion of a
- * handshake on this connection.
+ * Registers the specified listener to receive notification on completion of
+ * a handshake on this connection.
*
- * @param listener
- * the listener to register.
- * @throws IllegalArgumentException
- * if {@code listener} is {@code null}.
+ * @param listener the listener to register.
+ * @throws IllegalArgumentException if {@code listener} is {@code null}.
*/
public abstract void addHandshakeCompletedListener(HandshakeCompletedListener listener);
diff --git a/luni/src/main/java/javax/net/ssl/SSLSocketFactory.java b/luni/src/main/java/javax/net/ssl/SSLSocketFactory.java
index b07d0fd..b506fa6 100644
--- a/luni/src/main/java/javax/net/ssl/SSLSocketFactory.java
+++ b/luni/src/main/java/javax/net/ssl/SSLSocketFactory.java
@@ -22,6 +22,7 @@ import java.net.Socket;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import javax.net.SocketFactory;
+import org.apache.harmony.security.fortress.Services;
/**
* The abstract factory implementation to create {@code SSLSocket}s.
@@ -32,7 +33,7 @@ public abstract class SSLSocketFactory extends SocketFactory {
// The default SSL socket factory
private static SocketFactory defaultSocketFactory;
- private static String defaultName;
+ private static int lastCacheVersion = -1;
/**
* Returns the default {@code SSLSocketFactory} instance. The default is
@@ -41,23 +42,39 @@ public abstract class SSLSocketFactory extends SocketFactory {
* @return the default ssl socket factory instance.
*/
public static synchronized SocketFactory getDefault() {
- if (defaultSocketFactory != null) {
+ int newCacheVersion = Services.getCacheVersion();
+ if (defaultSocketFactory != null && lastCacheVersion == newCacheVersion) {
return defaultSocketFactory;
}
- if (defaultName == null) {
- defaultName = Security.getProperty("ssl.SocketFactory.provider");
- if (defaultName != null) {
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- if (cl == null) {
- cl = ClassLoader.getSystemClassLoader();
- }
- try {
- final Class<?> sfc = Class.forName(defaultName, true, cl);
- defaultSocketFactory = (SocketFactory) sfc.newInstance();
- } catch (Exception e) {
- System.logE("Problem creating " + defaultName, e);
+ lastCacheVersion = newCacheVersion;
+
+ String newName = Security.getProperty("ssl.SocketFactory.provider");
+ if (newName != null) {
+ /* The cache could have been invalidated, but the provider name didn't change. This
+ * will be the most common state, so check for it early without resetting the default
+ * SocketFactory.
+ */
+ if (defaultSocketFactory != null) {
+ if (newName.equals(defaultSocketFactory.getClass().getName())) {
+ return defaultSocketFactory;
+ } else {
+ defaultSocketFactory = null;
}
}
+
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ if (cl == null) {
+ cl = ClassLoader.getSystemClassLoader();
+ }
+ try {
+ final Class<?> sfc = Class.forName(newName, true, cl);
+ defaultSocketFactory = (SocketFactory) sfc.newInstance();
+ } catch (Exception e) {
+ System.logW("Could not create " + newName + " with ClassLoader "
+ + cl.toString() + ": " + e.getMessage());
+ }
+ } else {
+ defaultSocketFactory = null;
}
if (defaultSocketFactory == null) {
@@ -71,10 +88,12 @@ public abstract class SSLSocketFactory extends SocketFactory {
defaultSocketFactory = context.getSocketFactory();
}
}
+
if (defaultSocketFactory == null) {
// Use internal implementation
defaultSocketFactory = new DefaultSSLSocketFactory("No SSLSocketFactory installed");
}
+
return defaultSocketFactory;
}
diff --git a/luni/src/main/java/javax/security/cert/Certificate.java b/luni/src/main/java/javax/security/cert/Certificate.java
index b3e31f6..08ce36b 100644
--- a/luni/src/main/java/javax/security/cert/Certificate.java
+++ b/luni/src/main/java/javax/security/cert/Certificate.java
@@ -126,15 +126,15 @@ public abstract class Certificate {
* public key for which verification should be performed.
* @param sigProvider
* the name of the signature provider.
- * @exception CertificateException
+ * @throws CertificateException
* if encoding errors are detected
- * @exception NoSuchAlgorithmException
+ * @throws NoSuchAlgorithmException
* if an unsupported algorithm is detected
- * @exception InvalidKeyException
+ * @throws InvalidKeyException
* if an invalid key is detected
- * @exception NoSuchProviderException
+ * @throws NoSuchProviderException
* if the specified provider does not exists.
- * @exception SignatureException
+ * @throws SignatureException
* if signature errors are detected
*/
public abstract void verify(PublicKey key, String sigProvider)
@@ -157,4 +157,3 @@ public abstract class Certificate {
*/
public abstract PublicKey getPublicKey();
}
-
diff --git a/luni/src/main/java/javax/xml/transform/overview.html b/luni/src/main/java/javax/xml/transform/overview.html
index 918db9b..fe3372b 100644
--- a/luni/src/main/java/javax/xml/transform/overview.html
+++ b/luni/src/main/java/javax/xml/transform/overview.html
@@ -177,7 +177,7 @@
<H3>TRaX Patterns</H3>
<ul>
<p>
-<b><a name="pattern-Processor">Processor</a></b>
+<b><a name="pattern-Processor"></a>Processor</b>
<br>
<br>
<i>Intent: </i>Generic concept for the
@@ -191,7 +191,7 @@
operations. Different Processors can be used concurrently by different
threads.</p>
<p>
-<b><a name="pattern-TransformerFactory">TransformerFactory</a></b>
+<b><a name="pattern-TransformerFactory"></a>TransformerFactory</b>
<br>
<br>
<i>Intent: </i>Serve as a vendor-neutral Processor interface for
@@ -205,7 +205,7 @@
TransformerFactory may not perform multiple concurrent
operations.</p>
<p>
-<b><a name="pattern-Templates">Templates</a></b>
+<b><a name="pattern-Templates"></a>Templates</b>
<br>
<br>
<i>Intent: </i>The
@@ -215,7 +215,7 @@
<i>Thread safety: </i>Thread-safe for concurrent
usage over multiple threads once construction is complete.</p>
<p>
-<b><a name="pattern-Transformer">Transformer</a></b>
+<b><a name="pattern-Transformer"></a>Transformer</b>
<br>
<br>
<i>Intent: </i>Act as a per-thread
@@ -228,7 +228,7 @@
<i>Notes: </i>The Transformer is bound to the Templates
object that created it.</p>
<p>
-<b><a name="pattern-Source">Source</a></b>
+<b><a name="pattern-Source"></a>Source</b>
<br>
<br>
<i>Intent: </i>Serve as a
@@ -239,7 +239,7 @@
threads for read-only operations; must be synchronized for edit
operations.</p>
<p>
-<b><a name="pattern-Result">Result</a></b>
+<b><a name="pattern-Result"></a>Result</b>
<br>
<br>
<i>Potential alternate name: </i>ResultTarget<br>
diff --git a/luni/src/main/java/libcore/icu/CollationElementIteratorICU.java b/luni/src/main/java/libcore/icu/CollationElementIteratorICU.java
index 5779d17..d9f105d 100644
--- a/luni/src/main/java/libcore/icu/CollationElementIteratorICU.java
+++ b/luni/src/main/java/libcore/icu/CollationElementIteratorICU.java
@@ -37,15 +37,6 @@ import java.text.CharacterIterator;
* @stable ICU 2.4
*/
public final class CollationElementIteratorICU {
- // public data member -------------------------------------------
-
- /**
- * @stable ICU 2.4
- */
- public static final int NULLORDER = 0xFFFFFFFF;
-
- // public methods -----------------------------------------------
-
/**
* Reset the collation elements to their initial state.
* This will move the 'cursor' to the beginning of the text.
diff --git a/luni/src/main/java/libcore/icu/DateIntervalFormat.java b/luni/src/main/java/libcore/icu/DateIntervalFormat.java
index ab9085f..3855654 100644
--- a/luni/src/main/java/libcore/icu/DateIntervalFormat.java
+++ b/luni/src/main/java/libcore/icu/DateIntervalFormat.java
@@ -92,7 +92,7 @@ public final class DateIntervalFormat {
// This is not the behavior of icu4c's DateIntervalFormat, but it's the historical behavior
// of Android's DateUtils.formatDateRange.
if (startMs != endMs && endsAtMidnight &&
- ((flags & FORMAT_SHOW_TIME) == 0 || julianDay(startCalendar) == julianDay(endCalendar))) {
+ ((flags & FORMAT_SHOW_TIME) == 0 || dayDistance(startCalendar, endCalendar) <= 1)) {
endCalendar.roll(Calendar.DAY_OF_MONTH, false);
endMs -= DAY_IN_MS;
}
@@ -224,8 +224,12 @@ public final class DateIntervalFormat {
return c.get(Calendar.YEAR) == now.get(Calendar.YEAR);
}
+ private static int dayDistance(Calendar c1, Calendar c2) {
+ return julianDay(c2) - julianDay(c1);
+ }
+
private static int julianDay(Calendar c) {
- long utcMs = c.get(Calendar.MILLISECOND) + c.get(Calendar.ZONE_OFFSET);
+ long utcMs = c.getTimeInMillis() + c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET);
return (int) (utcMs / DAY_IN_MS) + EPOCH_JULIAN_DAY;
}
diff --git a/luni/src/main/java/libcore/icu/ICU.java b/luni/src/main/java/libcore/icu/ICU.java
index 76d9c54..0ef3f93 100644
--- a/luni/src/main/java/libcore/icu/ICU.java
+++ b/luni/src/main/java/libcore/icu/ICU.java
@@ -16,8 +16,13 @@
package libcore.icu;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
import libcore.util.BasicLruCache;
/**
@@ -53,30 +58,173 @@ public final class ICU {
return isoCountries.clone();
}
+ private static final int IDX_LANGUAGE = 0;
+ private static final int IDX_SCRIPT = 1;
+ private static final int IDX_REGION = 2;
+ private static final int IDX_VARIANT = 3;
+
+ /*
+ * Parse the {Language, Script, Region, Variant*} section of the ICU locale
+ * ID. This is the bit that appears before the keyword separate "@". The general
+ * structure is a series of ASCII alphanumeric strings (subtags)
+ * separated by underscores.
+ *
+ * Each subtag is interpreted according to its position in the list of subtags
+ * AND its length (groan...). The various cases are explained in comments
+ * below.
+ */
+ private static void parseLangScriptRegionAndVariants(String string,
+ String[] outputArray) {
+ final int first = string.indexOf('_');
+ final int second = string.indexOf('_', first + 1);
+ final int third = string.indexOf('_', second + 1);
+
+ if (first == -1) {
+ outputArray[IDX_LANGUAGE] = string;
+ } else if (second == -1) {
+ // Language and country ("ja_JP") OR
+ // Language and script ("en_Latn") OR
+ // Language and variant ("en_POSIX").
+
+ outputArray[IDX_LANGUAGE] = string.substring(0, first);
+ final String secondString = string.substring(first + 1);
+
+ if (secondString.length() == 4) {
+ // 4 Letter ISO script code.
+ outputArray[IDX_SCRIPT] = secondString;
+ } else if (secondString.length() == 2 || secondString.length() == 3) {
+ // 2 or 3 Letter region code.
+ outputArray[IDX_REGION] = secondString;
+ } else {
+ // If we're here, the length of the second half is either 1 or greater
+ // than 5. Assume that ICU won't hand us malformed tags, and therefore
+ // assume the rest of the string is a series of variant tags.
+ outputArray[IDX_VARIANT] = secondString;
+ }
+ } else if (third == -1) {
+ // Language and country and variant ("ja_JP_TRADITIONAL") OR
+ // Language and script and variant ("en_Latn_POSIX") OR
+ // Language and script and region ("en_Latn_US"). OR
+ // Language and variant with multiple subtags ("en_POSIX_XISOP")
+
+ outputArray[IDX_LANGUAGE] = string.substring(0, first);
+ final String secondString = string.substring(first + 1, second);
+ final String thirdString = string.substring(second + 1);
+
+ if (secondString.length() == 4) {
+ // The second subtag is a script.
+ outputArray[IDX_SCRIPT] = secondString;
+
+ // The third subtag can be either a region or a variant, depending
+ // on its length.
+ if (thirdString.length() == 2 || thirdString.length() == 3 ||
+ thirdString.isEmpty()) {
+ outputArray[IDX_REGION] = thirdString;
+ } else {
+ outputArray[IDX_VARIANT] = thirdString;
+ }
+ } else if (secondString.isEmpty() ||
+ secondString.length() == 2 || secondString.length() == 3) {
+ // The second string is a region, and the third a variant.
+ outputArray[IDX_REGION] = secondString;
+ outputArray[IDX_VARIANT] = thirdString;
+ } else {
+ // Variant with multiple subtags.
+ outputArray[IDX_VARIANT] = string.substring(first + 1);
+ }
+ } else {
+ // Language, script, region and variant with 1 or more subtags
+ // ("en_Latn_US_POSIX") OR
+ // Language, region and variant with 2 or more subtags
+ // (en_US_POSIX_VARIANT).
+ outputArray[IDX_LANGUAGE] = string.substring(0, first);
+ final String secondString = string.substring(first + 1, second);
+ if (secondString.length() == 4) {
+ outputArray[IDX_SCRIPT] = secondString;
+ outputArray[IDX_REGION] = string.substring(second + 1, third);
+ outputArray[IDX_VARIANT] = string.substring(third + 1);
+ } else {
+ outputArray[IDX_REGION] = secondString;
+ outputArray[IDX_VARIANT] = string.substring(second + 1);
+ }
+ }
+ }
+
/**
* Returns the appropriate {@code Locale} given a {@code String} of the form returned
* by {@code toString}. This is very lenient, and doesn't care what's between the underscores:
* this method can parse strings that {@code Locale.toString} won't produce.
* Used to remove duplication.
*/
- public static Locale localeFromString(String localeName) {
- int first = localeName.indexOf('_');
- int second = localeName.indexOf('_', first + 1);
- if (first == -1) {
- // Language only ("ja").
- return new Locale(localeName);
- } else if (second == -1) {
- // Language and country ("ja_JP").
- String language = localeName.substring(0, first);
- String country = localeName.substring(first + 1);
- return new Locale(language, country);
+ public static Locale localeFromIcuLocaleId(String localeId) {
+ // @ == ULOC_KEYWORD_SEPARATOR_UNICODE (uloc.h).
+ final int extensionsIndex = localeId.indexOf('@');
+
+ Map<Character, String> extensionsMap = Collections.EMPTY_MAP;
+ Map<String, String> unicodeKeywordsMap = Collections.EMPTY_MAP;
+ Set<String> unicodeAttributeSet = Collections.EMPTY_SET;
+
+ if (extensionsIndex != -1) {
+ extensionsMap = new HashMap<Character, String>();
+ unicodeKeywordsMap = new HashMap<String, String>();
+ unicodeAttributeSet = new HashSet<String>();
+
+ // ICU sends us a semi-colon (ULOC_KEYWORD_ITEM_SEPARATOR) delimited string
+ // containing all "keywords" it could parse. An ICU keyword is a key-value pair
+ // separated by an "=" (ULOC_KEYWORD_ASSIGN).
+ //
+ // Each keyword item can be one of three things :
+ // - A unicode extension attribute list: In this case the item key is "attribute"
+ // and the value is a hyphen separated list of unicode attributes.
+ // - A unicode extension keyword: In this case, the item key will be larger than
+ // 1 char in length, and the value will be the unicode extension value.
+ // - A BCP-47 extension subtag: In this case, the item key will be exactly one
+ // char in length, and the value will be a sequence of unparsed subtags that
+ // represent the extension.
+ //
+ // Note that this implies that unicode extension keywords are "promoted" to
+ // to the same namespace as the top level extension subtags and their values.
+ // There can't be any collisions in practice because the BCP-47 spec imposes
+ // restrictions on their lengths.
+ final String extensionsString = localeId.substring(extensionsIndex + 1);
+ final String[] extensions = extensionsString.split(";");
+ for (String extension : extensions) {
+ // This is the special key for the unicode attributes
+ if (extension.startsWith("attribute=")) {
+ String unicodeAttributeValues = extension.substring("attribute=".length());
+ for (String unicodeAttribute : unicodeAttributeValues.split("-")) {
+ unicodeAttributeSet.add(unicodeAttribute);
+ }
+ } else {
+ final int separatorIndex = extension.indexOf('=');
+
+ if (separatorIndex == 1) {
+ // This is a BCP-47 extension subtag.
+ final String value = extension.substring(2);
+ final char extensionId = extension.charAt(0);
+
+ extensionsMap.put(extensionId, value);
+ } else {
+ // This is a unicode extension keyword.
+ unicodeKeywordsMap.put(extension.substring(0, separatorIndex),
+ extension.substring(separatorIndex + 1));
+ }
+ }
+ }
+ }
+
+ final String[] outputArray = new String[] { "", "", "", "" };
+ if (extensionsIndex == -1) {
+ parseLangScriptRegionAndVariants(localeId, outputArray);
} else {
- // Language and country and variant ("ja_JP_TRADITIONAL").
- String language = localeName.substring(0, first);
- String country = localeName.substring(first + 1, second);
- String variant = localeName.substring(second + 1);
- return new Locale(language, country, variant);
+ parseLangScriptRegionAndVariants(localeId.substring(0, extensionsIndex),
+ outputArray);
}
+
+ return new Locale(outputArray[IDX_LANGUAGE], outputArray[IDX_REGION],
+ outputArray[IDX_VARIANT], outputArray[IDX_SCRIPT],
+ unicodeAttributeSet, unicodeKeywordsMap, extensionsMap,
+ true /* has validated fields */);
}
public static Locale[] localesFromStrings(String[] localeNames) {
@@ -85,7 +233,7 @@ public final class ICU {
// both so that we never need to convert back when talking to it.
LinkedHashSet<Locale> set = new LinkedHashSet<Locale>();
for (String localeName : localeNames) {
- set.add(localeFromString(localeName));
+ set.add(localeFromIcuLocaleId(localeName));
}
return set.toArray(new Locale[set.size()]);
}
@@ -125,19 +273,20 @@ public final class ICU {
return localesFromStrings(getAvailableNumberFormatLocalesNative());
}
- public static String getBestDateTimePattern(String skeleton, String localeName) {
- String key = skeleton + "\t" + localeName;
+ public static String getBestDateTimePattern(String skeleton, Locale locale) {
+ String languageTag = locale.toLanguageTag();
+ String key = skeleton + "\t" + languageTag;
synchronized (CACHED_PATTERNS) {
String pattern = CACHED_PATTERNS.get(key);
if (pattern == null) {
- pattern = getBestDateTimePatternNative(skeleton, localeName);
+ pattern = getBestDateTimePatternNative(skeleton, languageTag);
CACHED_PATTERNS.put(key, pattern);
}
return pattern;
}
}
- private static native String getBestDateTimePatternNative(String skeleton, String localeName);
+ private static native String getBestDateTimePatternNative(String skeleton, String languageTag);
public static char[] getDateFormatOrder(String pattern) {
char[] result = new char[3];
@@ -197,8 +346,17 @@ public final class ICU {
// --- Case mapping.
- public static native String toLowerCase(String s, String localeName);
- public static native String toUpperCase(String s, String localeName);
+ public static String toLowerCase(String s, Locale locale) {
+ return toLowerCase(s, locale.toLanguageTag());
+ }
+
+ private static native String toLowerCase(String s, String languageTag);
+
+ public static String toUpperCase(String s, Locale locale) {
+ return toUpperCase(s, locale.toLanguageTag());
+ }
+
+ private static native String toUpperCase(String s, String languageTag);
// --- Errors.
@@ -224,22 +382,79 @@ public final class ICU {
public static native String[] getAvailableCurrencyCodes();
public static native String getCurrencyCode(String countryCode);
- public static native String getCurrencyDisplayName(String locale, String currencyCode);
+
+ public static String getCurrencyDisplayName(Locale locale, String currencyCode) {
+ return getCurrencyDisplayName(locale.toLanguageTag(), currencyCode);
+ }
+
+ private static native String getCurrencyDisplayName(String languageTag, String currencyCode);
+
public static native int getCurrencyFractionDigits(String currencyCode);
- public static native String getCurrencySymbol(String locale, String currencyCode);
+ public static native int getCurrencyNumericCode(String currencyCode);
+
+ public static String getCurrencySymbol(Locale locale, String currencyCode) {
+ return getCurrencySymbol(locale.toLanguageTag(), currencyCode);
+ }
+
+ private static native String getCurrencySymbol(String languageTag, String currencyCode);
+
+ public static String getDisplayCountry(Locale targetLocale, Locale locale) {
+ return getDisplayCountryNative(targetLocale.toLanguageTag(), locale.toLanguageTag());
+ }
- public static native String getDisplayCountryNative(String countryCode, String locale);
- public static native String getDisplayLanguageNative(String languageCode, String locale);
- public static native String getDisplayVariantNative(String variantCode, String locale);
+ private static native String getDisplayCountryNative(String targetLanguageTag, String languageTag);
+
+ public static String getDisplayLanguage(Locale targetLocale, Locale locale) {
+ return getDisplayLanguageNative(targetLocale.toLanguageTag(), locale.toLanguageTag());
+ }
+
+ private static native String getDisplayLanguageNative(String targetLanguageTag, String languageTag);
+
+ public static String getDisplayVariant(Locale targetLocale, Locale locale) {
+ return getDisplayVariantNative(targetLocale.toLanguageTag(), locale.toLanguageTag());
+ }
+
+ private static native String getDisplayVariantNative(String targetLanguageTag, String languageTag);
+
+ public static String getDisplayScript(Locale targetLocale, Locale locale) {
+ return getDisplayScriptNative(targetLocale.toLanguageTag(), locale.toLanguageTag());
+ }
- public static native String getISO3CountryNative(String locale);
- public static native String getISO3LanguageNative(String locale);
+ private static native String getDisplayScriptNative(String targetLanguageTag, String languageTag);
+ public static native String getISO3Country(String languageTag);
+
+ public static native String getISO3Language(String languageTag);
+
+ public static Locale addLikelySubtags(Locale locale) {
+ return Locale.forLanguageTag(addLikelySubtags(locale.toLanguageTag()).replace('_', '-'));
+ }
+
+ /**
+ * @deprecated use {@link #addLikelySubtags(java.util.Locale)} instead.
+ */
+ @Deprecated
public static native String addLikelySubtags(String locale);
+
+ /**
+ * @deprecated use {@link java.util.Locale#getScript()} instead. This has been kept
+ * around only for the support library.
+ */
+ @Deprecated
public static native String getScript(String locale);
private static native String[] getISOLanguagesNative();
private static native String[] getISOCountriesNative();
- static native boolean initLocaleDataNative(String locale, LocaleData result);
+ static native boolean initLocaleDataNative(String languageTag, LocaleData result);
+
+ /**
+ * Takes a BCP-47 language tag (Locale.toLanguageTag()). e.g. en-US, not en_US
+ */
+ public static native void setDefaultLocale(String languageTag);
+
+ /**
+ * Returns a locale name, not a BCP-47 language tag. e.g. en_US not en-US.
+ */
+ public static native String getDefaultLocale();
}
diff --git a/luni/src/main/java/libcore/icu/LocaleData.java b/luni/src/main/java/libcore/icu/LocaleData.java
index f00c30f..9e07244 100644
--- a/luni/src/main/java/libcore/icu/LocaleData.java
+++ b/luni/src/main/java/libcore/icu/LocaleData.java
@@ -79,6 +79,10 @@ public final class LocaleData {
public String mediumDateFormat;
public String shortDateFormat;
+ // Used by TimePicker. Not currently used by UTS#35.
+ public String narrowAm; // "a".
+ public String narrowPm; // "p".
+
// shortDateFormat, but guaranteed to have 4-digit years.
// Used by android.text.format.DateFormat.getDateFormatStringForSetting.
public String shortDateFormat4;
@@ -95,7 +99,7 @@ public final class LocaleData {
public char percent;
public char perMill;
public char monetarySeparator;
- public char minusSign;
+ public String minusSign;
public String exponentSeparator;
public String infinity;
public String NaN;
@@ -112,27 +116,40 @@ public final class LocaleData {
private LocaleData() {
}
+ public static Locale mapInvalidAndNullLocales(Locale locale) {
+ if (locale == null) {
+ return Locale.getDefault();
+ }
+
+ if ("und".equals(locale.toLanguageTag())) {
+ return Locale.ROOT;
+ }
+
+ return locale;
+ }
+
/**
* Returns a shared LocaleData for the given locale.
*/
public static LocaleData get(Locale locale) {
if (locale == null) {
- locale = Locale.getDefault();
+ throw new NullPointerException("locale == null");
}
- String localeName = locale.toString();
+
+ final String languageTag = locale.toLanguageTag();
synchronized (localeDataCache) {
- LocaleData localeData = localeDataCache.get(localeName);
+ LocaleData localeData = localeDataCache.get(languageTag);
if (localeData != null) {
return localeData;
}
}
LocaleData newLocaleData = initLocaleData(locale);
synchronized (localeDataCache) {
- LocaleData localeData = localeDataCache.get(localeName);
+ LocaleData localeData = localeDataCache.get(languageTag);
if (localeData != null) {
return localeData;
}
- localeDataCache.put(localeName, newLocaleData);
+ localeDataCache.put(languageTag, newLocaleData);
return newLocaleData;
}
}
@@ -171,13 +188,13 @@ public final class LocaleData {
private static LocaleData initLocaleData(Locale locale) {
LocaleData localeData = new LocaleData();
- if (!ICU.initLocaleDataNative(locale.toString(), localeData)) {
+ if (!ICU.initLocaleDataNative(locale.toLanguageTag(), localeData)) {
throw new AssertionError("couldn't initialize LocaleData for locale " + locale);
}
// Get the "h:mm a" and "HH:mm" 12- and 24-hour time format strings.
- localeData.timeFormat12 = ICU.getBestDateTimePattern("hm", locale.toString());
- localeData.timeFormat24 = ICU.getBestDateTimePattern("Hm", locale.toString());
+ localeData.timeFormat12 = ICU.getBestDateTimePattern("hm", locale);
+ localeData.timeFormat24 = ICU.getBestDateTimePattern("Hm", locale);
// Fix up a couple of patterns.
if (localeData.fullTimeFormat != null) {
diff --git a/luni/src/main/java/libcore/icu/NativeBreakIterator.java b/luni/src/main/java/libcore/icu/NativeBreakIterator.java
index 7168d96..992aac2 100644
--- a/luni/src/main/java/libcore/icu/NativeBreakIterator.java
+++ b/luni/src/main/java/libcore/icu/NativeBreakIterator.java
@@ -138,23 +138,23 @@ public final class NativeBreakIterator implements Cloneable {
}
public int preceding(int offset) {
- return precedingImpl(this.address, this.string, offset);
+ return precedingImpl(this.address, this.string, offset);
}
- public static NativeBreakIterator getCharacterInstance(Locale where) {
- return new NativeBreakIterator(getCharacterInstanceImpl(where.toString()), BI_CHAR_INSTANCE);
+ public static NativeBreakIterator getCharacterInstance(Locale locale) {
+ return new NativeBreakIterator(getCharacterInstanceImpl(locale.toLanguageTag()), BI_CHAR_INSTANCE);
}
- public static NativeBreakIterator getLineInstance(Locale where) {
- return new NativeBreakIterator(getLineInstanceImpl(where.toString()), BI_LINE_INSTANCE);
+ public static NativeBreakIterator getLineInstance(Locale locale) {
+ return new NativeBreakIterator(getLineInstanceImpl(locale.toLanguageTag()), BI_LINE_INSTANCE);
}
- public static NativeBreakIterator getSentenceInstance(Locale where) {
- return new NativeBreakIterator(getSentenceInstanceImpl(where.toString()), BI_SENT_INSTANCE);
+ public static NativeBreakIterator getSentenceInstance(Locale locale) {
+ return new NativeBreakIterator(getSentenceInstanceImpl(locale.toLanguageTag()), BI_SENT_INSTANCE);
}
- public static NativeBreakIterator getWordInstance(Locale where) {
- return new NativeBreakIterator(getWordInstanceImpl(where.toString()), BI_WORD_INSTANCE);
+ public static NativeBreakIterator getWordInstance(Locale locale) {
+ return new NativeBreakIterator(getWordInstanceImpl(locale.toLanguageTag()), BI_WORD_INSTANCE);
}
private static native long getCharacterInstanceImpl(String locale);
diff --git a/luni/src/main/java/libcore/icu/NativeCollation.java b/luni/src/main/java/libcore/icu/NativeCollation.java
index 0373fef..b4b4f46 100644
--- a/luni/src/main/java/libcore/icu/NativeCollation.java
+++ b/luni/src/main/java/libcore/icu/NativeCollation.java
@@ -10,6 +10,8 @@
package libcore.icu;
+import java.util.Locale;
+
/**
* Package static class for declaring all native methods for collation use.
* @author syn wee quek
@@ -23,10 +25,13 @@ public final class NativeCollation {
public static native void closeCollator(long address);
public static native int compare(long address, String source, String target);
public static native int getAttribute(long address, int type);
- public static native int getCollationElementIterator(long address, String source);
+ public static native long getCollationElementIterator(long address, String source);
public static native String getRules(long address);
public static native byte[] getSortKey(long address, String source);
- public static native long openCollator(String locale);
+ public static long openCollator(Locale locale) {
+ return openCollator(locale.toLanguageTag());
+ }
+ private static native long openCollator(String languageTag);
public static native long openCollatorFromRules(String rules, int normalizationMode, int collationStrength);
public static native long safeClone(long address);
public static native void setAttribute(long address, int type, int value);
diff --git a/luni/src/main/java/libcore/icu/NativeDecimalFormat.java b/luni/src/main/java/libcore/icu/NativeDecimalFormat.java
index 0e9ffc4..fd179c1 100644
--- a/luni/src/main/java/libcore/icu/NativeDecimalFormat.java
+++ b/luni/src/main/java/libcore/icu/NativeDecimalFormat.java
@@ -27,7 +27,6 @@ import java.text.Format;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Currency;
-import java.util.NoSuchElementException;
public final class NativeDecimalFormat implements Cloneable {
/**
@@ -92,6 +91,47 @@ public final class NativeDecimalFormat implements Cloneable {
private static final int UNUM_PUBLIC_RULESETS = 7;
/**
+ * A table for translating between NumberFormat.Field instances
+ * and icu4c UNUM_x_FIELD constants.
+ */
+ private static final Format.Field[] ICU4C_FIELD_IDS = {
+ // The old java field values were 0 for integer and 1 for fraction.
+ // The new java field attributes are all objects. ICU assigns the values
+ // starting from 0 in the following order; note that integer and
+ // fraction positions match the old field values.
+ NumberFormat.Field.INTEGER, // 0 UNUM_INTEGER_FIELD
+ NumberFormat.Field.FRACTION, // 1 UNUM_FRACTION_FIELD
+ NumberFormat.Field.DECIMAL_SEPARATOR, // 2 UNUM_DECIMAL_SEPARATOR_FIELD
+ NumberFormat.Field.EXPONENT_SYMBOL, // 3 UNUM_EXPONENT_SYMBOL_FIELD
+ NumberFormat.Field.EXPONENT_SIGN, // 4 UNUM_EXPONENT_SIGN_FIELD
+ NumberFormat.Field.EXPONENT, // 5 UNUM_EXPONENT_FIELD
+ NumberFormat.Field.GROUPING_SEPARATOR, // 6 UNUM_GROUPING_SEPARATOR_FIELD
+ NumberFormat.Field.CURRENCY, // 7 UNUM_CURRENCY_FIELD
+ NumberFormat.Field.PERCENT, // 8 UNUM_PERCENT_FIELD
+ NumberFormat.Field.PERMILLE, // 9 UNUM_PERMILL_FIELD
+ NumberFormat.Field.SIGN, // 10 UNUM_SIGN_FIELD
+ };
+
+ private static int translateFieldId(FieldPosition fp) {
+ int id = fp.getField();
+ if (id < -1 || id > 1) {
+ id = -1;
+ }
+ if (id == -1) {
+ Format.Field attr = fp.getFieldAttribute();
+ if (attr != null) {
+ for (int i = 0; i < ICU4C_FIELD_IDS.length; ++i) {
+ if (ICU4C_FIELD_IDS[i].equals(attr)) {
+ id = i;
+ break;
+ }
+ }
+ }
+ }
+ return id;
+ }
+
+ /**
* The address of the ICU DecimalFormat* on the native heap.
*/
private long address;
@@ -111,19 +151,12 @@ public final class NativeDecimalFormat implements Cloneable {
private transient boolean parseBigDecimal;
- /**
- * Cache the BigDecimal form of the multiplier. This is null until we've
- * formatted a BigDecimal (with a multiplier that is not 1), or the user has
- * explicitly called {@link #setMultiplier(int)} with any multiplier.
- */
- private BigDecimal multiplierBigDecimal = null;
-
public NativeDecimalFormat(String pattern, DecimalFormatSymbols dfs) {
try {
this.address = open(pattern, dfs.getCurrencySymbol(),
dfs.getDecimalSeparator(), dfs.getDigit(), dfs.getExponentSeparator(),
dfs.getGroupingSeparator(), dfs.getInfinity(),
- dfs.getInternationalCurrencySymbol(), dfs.getMinusSign(),
+ dfs.getInternationalCurrencySymbol(), dfs.getMinusSignString(),
dfs.getMonetaryDecimalSeparator(), dfs.getNaN(), dfs.getPatternSeparator(),
dfs.getPercent(), dfs.getPerMill(), dfs.getZeroDigit());
this.lastPattern = pattern;
@@ -211,13 +244,30 @@ public final class NativeDecimalFormat implements Cloneable {
obj.isGroupingUsed() == this.isGroupingUsed();
}
+ public String toString() {
+ return getClass().getName() + "[\"" + toPattern() + "\"" +
+ ",isDecimalSeparatorAlwaysShown=" + isDecimalSeparatorAlwaysShown() +
+ ",groupingSize=" + getGroupingSize() +
+ ",multiplier=" + getMultiplier() +
+ ",negativePrefix=" + getNegativePrefix() +
+ ",negativeSuffix=" + getNegativeSuffix() +
+ ",positivePrefix=" + getPositivePrefix() +
+ ",positiveSuffix=" + getPositiveSuffix() +
+ ",maxIntegerDigits=" + getMaximumIntegerDigits() +
+ ",maxFractionDigits=" + getMaximumFractionDigits() +
+ ",minIntegerDigits=" + getMinimumIntegerDigits() +
+ ",minFractionDigits=" + getMinimumFractionDigits() +
+ ",grouping=" + isGroupingUsed() +
+ "]";
+ }
+
/**
* Copies the DecimalFormatSymbols settings into our native peer in bulk.
*/
public void setDecimalFormatSymbols(final DecimalFormatSymbols dfs) {
setDecimalFormatSymbols(this.address, dfs.getCurrencySymbol(), dfs.getDecimalSeparator(),
dfs.getDigit(), dfs.getExponentSeparator(), dfs.getGroupingSeparator(),
- dfs.getInfinity(), dfs.getInternationalCurrencySymbol(), dfs.getMinusSign(),
+ dfs.getInfinity(), dfs.getInternationalCurrencySymbol(), dfs.getMinusSignString(),
dfs.getMonetaryDecimalSeparator(), dfs.getNaN(), dfs.getPatternSeparator(),
dfs.getPercent(), dfs.getPerMill(), dfs.getZeroDigit());
}
@@ -233,8 +283,8 @@ public final class NativeDecimalFormat implements Cloneable {
public char[] formatBigDecimal(BigDecimal value, FieldPosition field) {
FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field);
char[] result = formatDigitList(this.address, value.toString(), fpi);
- if (fpi != null) {
- FieldPositionIterator.setFieldPosition(fpi, field);
+ if (fpi != null && field != null) {
+ updateFieldPosition(field, fpi);
}
return result;
}
@@ -242,8 +292,8 @@ public final class NativeDecimalFormat implements Cloneable {
public char[] formatBigInteger(BigInteger value, FieldPosition field) {
FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field);
char[] result = formatDigitList(this.address, value.toString(10), fpi);
- if (fpi != null) {
- FieldPositionIterator.setFieldPosition(fpi, field);
+ if (fpi != null && field != null) {
+ updateFieldPosition(field, fpi);
}
return result;
}
@@ -251,8 +301,8 @@ public final class NativeDecimalFormat implements Cloneable {
public char[] formatLong(long value, FieldPosition field) {
FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field);
char[] result = formatLong(this.address, value, fpi);
- if (fpi != null) {
- FieldPositionIterator.setFieldPosition(fpi, field);
+ if (fpi != null && field != null) {
+ updateFieldPosition(field, fpi);
}
return result;
}
@@ -260,12 +310,25 @@ public final class NativeDecimalFormat implements Cloneable {
public char[] formatDouble(double value, FieldPosition field) {
FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field);
char[] result = formatDouble(this.address, value, fpi);
- if (fpi != null) {
- FieldPositionIterator.setFieldPosition(fpi, field);
+ if (fpi != null && field != null) {
+ updateFieldPosition(field, fpi);
}
return result;
}
+ private static void updateFieldPosition(FieldPosition fp, FieldPositionIterator fpi) {
+ int field = translateFieldId(fp);
+ if (field != -1) {
+ while (fpi.next()) {
+ if (fpi.fieldId() == field) {
+ fp.setBeginIndex(fpi.start());
+ fp.setEndIndex(fpi.limit());
+ return;
+ }
+ }
+ }
+ }
+
public void applyLocalizedPattern(String pattern) {
applyPattern(this.address, true, pattern);
lastPattern = null;
@@ -352,6 +415,10 @@ public final class NativeDecimalFormat implements Cloneable {
}
public int getGroupingSize() {
+ // Work around http://bugs.icu-project.org/trac/ticket/10864 in icu4c 53.
+ if (!isGroupingUsed()) {
+ return 0;
+ }
return getAttribute(this.address, UNUM_GROUPING_SIZE);
}
@@ -408,9 +475,9 @@ public final class NativeDecimalFormat implements Cloneable {
setAttribute(this.address, UNUM_DECIMAL_ALWAYS_SHOWN, i);
}
- public void setCurrency(Currency currency) {
- setSymbol(this.address, UNUM_CURRENCY_SYMBOL, currency.getSymbol());
- setSymbol(this.address, UNUM_INTL_CURRENCY_SYMBOL, currency.getCurrencyCode());
+ public void setCurrency(String currencySymbol, String currencyCode) {
+ setSymbol(this.address, UNUM_CURRENCY_SYMBOL, currencySymbol);
+ setSymbol(this.address, UNUM_INTL_CURRENCY_SYMBOL, currencyCode);
}
public void setGroupingSize(int value) {
@@ -440,8 +507,6 @@ public final class NativeDecimalFormat implements Cloneable {
public void setMultiplier(int value) {
setAttribute(this.address, UNUM_MULTIPLIER, value);
- // Update the cached BigDecimal for multiplier.
- multiplierBigDecimal = BigDecimal.valueOf(value);
}
public void setNegativePrefix(String value) {
@@ -501,6 +566,7 @@ public final class NativeDecimalFormat implements Cloneable {
case HALF_EVEN: nativeRoundingMode = 4; break;
case HALF_DOWN: nativeRoundingMode = 5; break;
case HALF_UP: nativeRoundingMode = 6; break;
+ case UNNECESSARY: nativeRoundingMode = 7; break;
default: throw new AssertionError();
}
setRoundingMode(address, nativeRoundingMode, roundingIncrement);
@@ -515,104 +581,33 @@ public final class NativeDecimalFormat implements Cloneable {
}
public static FieldPositionIterator forFieldPosition(FieldPosition fp) {
- if (fp != null && fp.getField() != -1) {
- return new FieldPositionIterator();
- }
- return null;
- }
-
- private static int getNativeFieldPositionId(FieldPosition fp) {
- // NOTE: -1, 0, and 1 were the only valid original java field values
- // for NumberFormat. They take precedence. This assumes any other
- // value is a mistake and the actual value is in the attribute.
- // Clients can construct FieldPosition combining any attribute with any field
- // value, which is just wrong, but there you go.
-
- int id = fp.getField();
- if (id < -1 || id > 1) {
- id = -1;
- }
- if (id == -1) {
- Format.Field attr = fp.getFieldAttribute();
- if (attr != null) {
- for (int i = 0; i < fields.length; ++i) {
- if (fields[i].equals(attr)) {
- id = i;
- break;
- }
- }
- }
- }
- return id;
- }
-
- private static void setFieldPosition(FieldPositionIterator fpi, FieldPosition fp) {
- if (fpi != null && fp != null) {
- int field = getNativeFieldPositionId(fp);
- if (field != -1) {
- while (fpi.next()) {
- if (fpi.fieldId() == field) {
- fp.setBeginIndex(fpi.start());
- fp.setEndIndex(fpi.limit());
- break;
- }
- }
- }
- }
+ return (fp != null) ? new FieldPositionIterator() : null;
}
public boolean next() {
- // if pos == data.length, we've already returned false once
- if (data == null || pos == data.length) {
- throw new NoSuchElementException();
+ if (data == null) {
+ return false;
}
pos += 3;
return pos < data.length;
}
- private void checkValid() {
- if (data == null || pos < 0 || pos == data.length) {
- throw new NoSuchElementException();
- }
- }
-
public int fieldId() {
return data[pos];
}
public Format.Field field() {
- checkValid();
- return fields[data[pos]];
+ return ICU4C_FIELD_IDS[data[pos]];
}
public int start() {
- checkValid();
return data[pos + 1];
}
public int limit() {
- checkValid();
return data[pos + 2];
}
- private static Format.Field fields[] = {
- // The old java field values were 0 for integer and 1 for fraction.
- // The new java field attributes are all objects. ICU assigns the values
- // starting from 0 in the following order; note that integer and
- // fraction positions match the old field values.
- NumberFormat.Field.INTEGER,
- NumberFormat.Field.FRACTION,
- NumberFormat.Field.DECIMAL_SEPARATOR,
- NumberFormat.Field.EXPONENT_SYMBOL,
- NumberFormat.Field.EXPONENT_SIGN,
- NumberFormat.Field.EXPONENT,
- NumberFormat.Field.GROUPING_SEPARATOR,
- NumberFormat.Field.CURRENCY,
- NumberFormat.Field.PERCENT,
- NumberFormat.Field.PERMILLE,
- NumberFormat.Field.SIGN,
- };
-
// called by native
private void setData(int[] data) {
this.data = data;
@@ -630,13 +625,13 @@ public final class NativeDecimalFormat implements Cloneable {
private static native String getTextAttribute(long addr, int symbol);
private static native long open(String pattern, String currencySymbol,
char decimalSeparator, char digit, String exponentSeparator, char groupingSeparator,
- String infinity, String internationalCurrencySymbol, char minusSign,
+ String infinity, String internationalCurrencySymbol, String minusSign,
char monetaryDecimalSeparator, String nan, char patternSeparator, char percent,
char perMill, char zeroDigit);
private static native Number parse(long addr, String string, ParsePosition position, boolean parseBigDecimal);
private static native void setDecimalFormatSymbols(long addr, String currencySymbol,
char decimalSeparator, char digit, String exponentSeparator, char groupingSeparator,
- String infinity, String internationalCurrencySymbol, char minusSign,
+ String infinity, String internationalCurrencySymbol, String minusSign,
char monetaryDecimalSeparator, String nan, char patternSeparator, char percent,
char perMill, char zeroDigit);
private static native void setSymbol(long addr, int symbol, String str);
diff --git a/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java b/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java
index 3ea942d..b23013b 100644
--- a/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java
+++ b/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java
@@ -52,7 +52,7 @@ public final class RuleBasedCollatorICU implements Cloneable {
}
public RuleBasedCollatorICU(Locale locale) {
- address = NativeCollation.openCollator(locale.toString());
+ address = NativeCollation.openCollator(locale);
}
private RuleBasedCollatorICU(long address) {
diff --git a/luni/src/main/java/libcore/icu/TimeZoneNames.java b/luni/src/main/java/libcore/icu/TimeZoneNames.java
index 5bb54a1..3413a5d 100644
--- a/luni/src/main/java/libcore/icu/TimeZoneNames.java
+++ b/luni/src/main/java/libcore/icu/TimeZoneNames.java
@@ -53,15 +53,8 @@ public final class TimeZoneNames {
}
public static class ZoneStringsCache extends BasicLruCache<Locale, String[][]> {
- // De-duplicate the strings (http://b/2672057).
- private final HashMap<String, String> internTable = new HashMap<String, String>();
-
public ZoneStringsCache() {
- // We make room for all the time zones known to the system, since each set of strings
- // isn't particularly large (and we remove duplicates), but is currently (Honeycomb)
- // really expensive to compute.
- // If you change this, you might want to change the scope of the intern table too.
- super(availableTimeZoneIds.length);
+ super(5); // Room for a handful of locales.
}
@Override protected String[][] create(Locale locale) {
@@ -85,11 +78,13 @@ public final class TimeZoneNames {
long nativeDuration = nativeEnd - nativeStart;
long duration = end - start;
System.logI("Loaded time zone names for \"" + locale + "\" in " + duration + "ms" +
- " (" + nativeDuration + "ms in ICU)");
+ " (" + nativeDuration + "ms in ICU)");
return result;
}
+ // De-duplicate the strings (http://b/2672057).
private synchronized void internStrings(String[][] result) {
+ HashMap<String, String> internTable = new HashMap<String, String>();
for (int i = 0; i < result.length; ++i) {
for (int j = 1; j < NAME_COUNT; ++j) {
String original = result[i][j];
@@ -162,5 +157,7 @@ public final class TimeZoneNames {
return ids.toArray(new String[ids.size()]);
}
+ public static native String getExemplarLocation(String locale, String tz);
+
private static native void fillZoneStrings(String locale, String[][] result);
}
diff --git a/luni/src/main/java/libcore/io/BlockGuardOs.java b/luni/src/main/java/libcore/io/BlockGuardOs.java
index c61a3cf..b3dc74b 100644
--- a/luni/src/main/java/libcore/io/BlockGuardOs.java
+++ b/luni/src/main/java/libcore/io/BlockGuardOs.java
@@ -16,14 +16,22 @@
package libcore.io;
+import android.system.ErrnoException;
+import android.system.StructLinger;
+import android.system.StructPollfd;
+import android.system.StructStat;
+import android.system.StructStatVfs;
+import android.util.MutableLong;
import dalvik.system.BlockGuard;
import dalvik.system.SocketTagger;
import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
+import static dalvik.system.BlockGuard.DISALLOW_NETWORK;
/**
* Informs BlockGuard of any activity it should be aware of.
@@ -55,9 +63,27 @@ public class BlockGuardOs extends ForwardingOs {
return tagSocket(os.accept(fd, peerAddress));
}
+ @Override public boolean access(String path, int mode) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onReadFromDisk();
+ return os.access(path, mode);
+ }
+
+ @Override public void chmod(String path, int mode) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.chmod(path, mode);
+ }
+
+ @Override public void chown(String path, int uid, int gid) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.chown(path, uid, gid);
+ }
+
@Override public void close(FileDescriptor fd) throws ErrnoException {
try {
- if (S_ISSOCK(Libcore.os.fstat(fd).st_mode)) {
+ // The usual case is that this _isn't_ a socket, so the getsockopt(2) call in
+ // isLingerSocket will throw, and that's really expensive. Try to avoid asking
+ // if we don't care.
+ if (fd.isSocket()) {
if (isLingerSocket(fd)) {
// If the fd is a socket with SO_LINGER set, we might block indefinitely.
// We allow non-linger sockets so that apps can close their network
@@ -85,6 +111,16 @@ public class BlockGuardOs extends ForwardingOs {
os.connect(fd, address, port);
}
+ @Override public void fchmod(FileDescriptor fd, int mode) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.fchmod(fd, mode);
+ }
+
+ @Override public void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.fchown(fd, uid, gid);
+ }
+
// TODO: Untag newFd when needed for dup2(FileDescriptor oldFd, int newFd)
@Override public void fdatasync(FileDescriptor fd) throws ErrnoException {
@@ -92,6 +128,16 @@ public class BlockGuardOs extends ForwardingOs {
os.fdatasync(fd);
}
+ @Override public StructStat fstat(FileDescriptor fd) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onReadFromDisk();
+ return os.fstat(fd);
+ }
+
+ @Override public StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onReadFromDisk();
+ return os.fstatvfs(fd);
+ }
+
@Override public void fsync(FileDescriptor fd) throws ErrnoException {
BlockGuard.getThreadPolicy().onWriteToDisk();
os.fsync(fd);
@@ -102,6 +148,36 @@ public class BlockGuardOs extends ForwardingOs {
os.ftruncate(fd, length);
}
+ @Override public void lchown(String path, int uid, int gid) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.lchown(path, uid, gid);
+ }
+
+ @Override public void link(String oldPath, String newPath) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.link(oldPath, newPath);
+ }
+
+ @Override public long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onReadFromDisk();
+ return os.lseek(fd, offset, whence);
+ }
+
+ @Override public StructStat lstat(String path) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onReadFromDisk();
+ return os.lstat(path);
+ }
+
+ @Override public void mkdir(String path, int mode) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.mkdir(path, mode);
+ }
+
+ @Override public void mkfifo(String path, int mode) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.mkfifo(path, mode);
+ }
+
@Override public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
BlockGuard.getThreadPolicy().onReadFromDisk();
if ((mode & O_ACCMODE) != O_RDONLY) {
@@ -119,37 +195,47 @@ public class BlockGuardOs extends ForwardingOs {
return os.poll(fds, timeoutMs);
}
- @Override public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException {
+ @Override public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.posix_fallocate(fd, offset, length);
+ }
+
+ @Override public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return os.pread(fd, buffer, offset);
}
- @Override public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException {
+ @Override public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return os.pread(fd, bytes, byteOffset, byteCount, offset);
}
- @Override public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException {
+ @Override public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return os.pwrite(fd, buffer, offset);
}
- @Override public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException {
+ @Override public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return os.pwrite(fd, bytes, byteOffset, byteCount, offset);
}
- @Override public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
+ @Override public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return os.read(fd, buffer);
}
- @Override public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+ @Override public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return os.read(fd, bytes, byteOffset, byteCount);
}
- @Override public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException {
+ @Override public String readlink(String path) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onReadFromDisk();
+ return os.readlink(path);
+ }
+
+ @Override public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return os.readv(fd, buffers, offsets, byteCounts);
}
@@ -164,6 +250,21 @@ public class BlockGuardOs extends ForwardingOs {
return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress);
}
+ @Override public void remove(String path) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.remove(path);
+ }
+
+ @Override public void rename(String oldPath, String newPath) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.rename(oldPath, newPath);
+ }
+
+ @Override public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ return os.sendfile(outFd, inFd, inOffset, byteCount);
+ }
+
@Override public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException {
BlockGuard.getThreadPolicy().onNetwork();
return os.sendto(fd, buffer, flags, inetAddress, port);
@@ -187,17 +288,32 @@ public class BlockGuardOs extends ForwardingOs {
tagSocket(fd2);
}
- @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
+ @Override public StructStat stat(String path) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onReadFromDisk();
+ return os.stat(path);
+ }
+
+ @Override public StructStatVfs statvfs(String path) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onReadFromDisk();
+ return os.statvfs(path);
+ }
+
+ @Override public void symlink(String oldPath, String newPath) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.symlink(oldPath, newPath);
+ }
+
+ @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return os.write(fd, buffer);
}
- @Override public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+ @Override public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return os.write(fd, bytes, byteOffset, byteCount);
}
- @Override public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException {
+ @Override public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return os.writev(fd, buffers, offsets, byteCounts);
}
diff --git a/luni/src/main/java/org/apache/harmony/luni/util/DeleteOnExit.java b/luni/src/main/java/libcore/io/DeleteOnExit.java
index 8fa04fd..36d7948 100644
--- a/luni/src/main/java/org/apache/harmony/luni/util/DeleteOnExit.java
+++ b/luni/src/main/java/libcore/io/DeleteOnExit.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.harmony.luni.util;
+package libcore.io;
import java.io.File;
@@ -34,11 +34,6 @@ public class DeleteOnExit extends Thread {
private static DeleteOnExit instance;
/**
- * Our list of files scheduled for deletion.
- */
- private ArrayList<String> files = new ArrayList<String>();
-
- /**
* Returns our singleton instance, creating it if necessary.
*/
public static synchronized DeleteOnExit getInstance() {
@@ -51,12 +46,21 @@ public class DeleteOnExit extends Thread {
}
/**
+ * Our list of files scheduled for deletion.
+ */
+ private final ArrayList<String> files = new ArrayList<String>();
+
+
+ private DeleteOnExit() {
+ }
+
+ /**
* Schedules a file for deletion.
*
* @param filename The file to delete.
*/
public void addFile(String filename) {
- synchronized(files) {
+ synchronized (files) {
if (!files.contains(filename)) {
files.add(filename);
}
diff --git a/luni/src/main/java/libcore/io/ErrnoException.java b/luni/src/main/java/libcore/io/ErrnoException.java
deleted file mode 100644
index f484ce9..0000000
--- a/luni/src/main/java/libcore/io/ErrnoException.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-import java.io.IOException;
-import java.net.SocketException;
-
-/**
- * A checked exception thrown when {@link Os} methods fail. This exception contains the native
- * errno value, for comparison against the constants in {@link OsConstants}, should sophisticated
- * callers need to adjust their behavior based on the exact failure.
- */
-public final class ErrnoException extends Exception {
- private final String functionName;
- public final int errno;
-
- public ErrnoException(String functionName, int errno) {
- this.functionName = functionName;
- this.errno = errno;
- }
-
- public ErrnoException(String functionName, int errno, Throwable cause) {
- super(cause);
- this.functionName = functionName;
- this.errno = errno;
- }
-
- /**
- * Converts the stashed function name and errno value to a human-readable string.
- * We do this here rather than in the constructor so that callers only pay for
- * this if they need it.
- */
- @Override public String getMessage() {
- String errnoName = OsConstants.errnoName(errno);
- if (errnoName == null) {
- errnoName = "errno " + errno;
- }
- String description = Libcore.os.strerror(errno);
- return functionName + " failed: " + errnoName + " (" + description + ")";
- }
-
- public IOException rethrowAsIOException() throws IOException {
- IOException newException = new IOException(getMessage());
- newException.initCause(this);
- throw newException;
- }
-
- public SocketException rethrowAsSocketException() throws SocketException {
- throw new SocketException(getMessage(), this);
- }
-}
diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java
index 3800416..bf4b448 100644
--- a/luni/src/main/java/libcore/io/ForwardingOs.java
+++ b/luni/src/main/java/libcore/io/ForwardingOs.java
@@ -16,14 +16,29 @@
package libcore.io;
+import android.system.ErrnoException;
+import android.system.GaiException;
+import android.system.StructAddrinfo;
+import android.system.StructFlock;
+import android.system.StructGroupReq;
+import android.system.StructGroupSourceReq;
+import android.system.StructLinger;
+import android.system.StructPasswd;
+import android.system.StructPollfd;
+import android.system.StructStat;
+import android.system.StructStatVfs;
+import android.system.StructTimeval;
+import android.system.StructUcred;
+import android.system.StructUtsname;
+import android.util.MutableInt;
+import android.util.MutableLong;
import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
-import libcore.util.MutableInt;
-import libcore.util.MutableLong;
/**
* Subclass this if you want to override some {@link Os} methods but otherwise delegate.
@@ -37,6 +52,7 @@ public class ForwardingOs implements Os {
public FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException { return os.accept(fd, peerAddress); }
public boolean access(String path, int mode) throws ErrnoException { return os.access(path, mode); }
+ public InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException { return os.android_getaddrinfo(node, hints, netId); }
public void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { os.bind(fd, address, port); }
public void chmod(String path, int mode) throws ErrnoException { os.chmod(path, mode); }
public void chown(String path, int uid, int gid) throws ErrnoException { os.chown(path, uid, gid); }
@@ -58,7 +74,6 @@ public class ForwardingOs implements Os {
public void fsync(FileDescriptor fd) throws ErrnoException { os.fsync(fd); }
public void ftruncate(FileDescriptor fd, long length) throws ErrnoException { os.ftruncate(fd, length); }
public String gai_strerror(int error) { return os.gai_strerror(error); }
- public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException { return os.getaddrinfo(node, hints); }
public int getegid() { return os.getegid(); }
public int geteuid() { return os.geteuid(); }
public int getgid() { return os.getgid(); }
@@ -85,11 +100,13 @@ public class ForwardingOs implements Os {
public boolean isatty(FileDescriptor fd) { return os.isatty(fd); }
public void kill(int pid, int signal) throws ErrnoException { os.kill(pid, signal); }
public void lchown(String path, int uid, int gid) throws ErrnoException { os.lchown(path, uid, gid); }
+ public void link(String oldPath, String newPath) throws ErrnoException { os.link(oldPath, newPath); }
public void listen(FileDescriptor fd, int backlog) throws ErrnoException { os.listen(fd, backlog); }
public long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return os.lseek(fd, offset, whence); }
public StructStat lstat(String path) throws ErrnoException { return os.lstat(path); }
public void mincore(long address, long byteCount, byte[] vector) throws ErrnoException { os.mincore(address, byteCount, vector); }
public void mkdir(String path, int mode) throws ErrnoException { os.mkdir(path, mode); }
+ public void mkfifo(String path, int mode) throws ErrnoException { os.mkfifo(path, mode); }
public void mlock(long address, long byteCount) throws ErrnoException { os.mlock(address, byteCount); }
public long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return os.mmap(address, byteCount, prot, flags, fd, offset); }
public void msync(long address, long byteCount, int flags) throws ErrnoException { os.msync(address, byteCount, flags); }
@@ -98,13 +115,16 @@ public class ForwardingOs implements Os {
public FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return os.open(path, flags, mode); }
public FileDescriptor[] pipe() throws ErrnoException { return os.pipe(); }
public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return os.poll(fds, timeoutMs); }
- public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { return os.pread(fd, buffer, offset); }
- public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { return os.pread(fd, bytes, byteOffset, byteCount, offset); }
- public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { return os.pwrite(fd, buffer, offset); }
- public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { return os.pwrite(fd, bytes, byteOffset, byteCount, offset); }
- public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.read(fd, buffer); }
- public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.read(fd, bytes, byteOffset, byteCount); }
- public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.readv(fd, buffers, offsets, byteCounts); }
+ public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { os.posix_fallocate(fd, offset, length); }
+ public int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException { return os.prctl(option, arg2, arg3, arg4, arg5); };
+ public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return os.pread(fd, buffer, offset); }
+ public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return os.pread(fd, bytes, byteOffset, byteCount, offset); }
+ public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return os.pwrite(fd, buffer, offset); }
+ public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return os.pwrite(fd, bytes, byteOffset, byteCount, offset); }
+ public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return os.read(fd, buffer); }
+ public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return os.read(fd, bytes, byteOffset, byteCount); }
+ public String readlink(String path) throws ErrnoException { return os.readlink(path); }
+ public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return os.readv(fd, buffers, offsets, byteCounts); }
public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return os.recvfrom(fd, buffer, flags, srcAddress); }
public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); }
public void remove(String path) throws ErrnoException { os.remove(path); }
@@ -122,6 +142,7 @@ public class ForwardingOs implements Os {
public void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptInt(fd, level, option, value); }
public void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptIpMreqn(fd, level, option, value); }
public void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { os.setsockoptGroupReq(fd, level, option, value); }
+ public void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException { os.setsockoptGroupSourceReq(fd, level, option, value); }
public void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { os.setsockoptLinger(fd, level, option, value); }
public void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { os.setsockoptTimeval(fd, level, option, value); }
public void setuid(int uid) throws ErrnoException { os.setuid(uid); }
@@ -140,7 +161,7 @@ public class ForwardingOs implements Os {
public StructUtsname uname() { return os.uname(); }
public void unsetenv(String name) throws ErrnoException { os.unsetenv(name); }
public int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return os.waitpid(pid, status, options); }
- public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.write(fd, buffer); }
- public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.write(fd, bytes, byteOffset, byteCount); }
- public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.writev(fd, buffers, offsets, byteCounts); }
+ public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return os.write(fd, buffer); }
+ public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return os.write(fd, bytes, byteOffset, byteCount); }
+ public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return os.writev(fd, buffers, offsets, byteCounts); }
}
diff --git a/luni/src/main/java/libcore/io/GaiException.java b/luni/src/main/java/libcore/io/GaiException.java
deleted file mode 100644
index 08143dc..0000000
--- a/luni/src/main/java/libcore/io/GaiException.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-import java.net.UnknownHostException;
-import libcore.io.OsConstants;
-
-/**
- * An unchecked exception thrown when the {@link Os} {@code getaddrinfo} or {@code getnameinfo}
- * methods fail. This exception contains the native error value, for comparison against the
- * {@code GAI_} constants in {@link OsConstants}, should sophisticated
- * callers need to adjust their behavior based on the exact failure.
- */
-public final class GaiException extends RuntimeException {
- private final String functionName;
- public final int error;
-
- public GaiException(String functionName, int error) {
- this.functionName = functionName;
- this.error = error;
- }
-
- public GaiException(String functionName, int error, Throwable cause) {
- super(cause);
- this.functionName = functionName;
- this.error = error;
- }
-
- /**
- * Converts the stashed function name and error value to a human-readable string.
- * We do this here rather than in the constructor so that callers only pay for
- * this if they need it.
- */
- @Override public String getMessage() {
- String gaiName = OsConstants.gaiName(error);
- if (gaiName == null) {
- gaiName = "GAI_ error " + error;
- }
- String description = Libcore.os.gai_strerror(error);
- return functionName + " failed: " + gaiName + " (" + description + ")";
- }
-
- public UnknownHostException rethrowAsUnknownHostException(String detailMessage) throws UnknownHostException {
- UnknownHostException newException = new UnknownHostException(detailMessage);
- newException.initCause(this);
- throw newException;
- }
-
- public UnknownHostException rethrowAsUnknownHostException() throws UnknownHostException {
- throw rethrowAsUnknownHostException(getMessage());
- }
-}
diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java
index 28adba1..acc8d4f 100644
--- a/luni/src/main/java/libcore/io/IoBridge.java
+++ b/luni/src/main/java/libcore/io/IoBridge.java
@@ -16,6 +16,13 @@
package libcore.io;
+import android.system.ErrnoException;
+import android.system.StructGroupReq;
+import android.system.StructGroupSourceReq;
+import android.system.StructLinger;
+import android.system.StructPollfd;
+import android.system.StructTimeval;
+import android.util.MutableInt;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -35,8 +42,7 @@ import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Arrays;
-import static libcore.io.OsConstants.*;
-import libcore.util.MutableInt;
+import static android.system.OsConstants.*;
/**
* Implements java.io/java.net/java.nio semantics in terms of the underlying POSIX system calls.
@@ -71,16 +77,20 @@ public final class IoBridge {
public static void bind(FileDescriptor fd, InetAddress address, int port) throws SocketException {
- if (address instanceof Inet6Address && ((Inet6Address) address).getScopeId() == 0) {
- // Linux won't let you bind a link-local address without a scope id. Find one.
- NetworkInterface nif = NetworkInterface.getByInetAddress(address);
- if (nif == null) {
- throw new SocketException("Can't bind to a link-local address without a scope id: " + address);
- }
- try {
- address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex());
- } catch (UnknownHostException ex) {
- throw new AssertionError(ex); // Can't happen.
+ if (address instanceof Inet6Address) {
+ Inet6Address inet6Address = (Inet6Address) address;
+ if (inet6Address.getScopeId() == 0 && inet6Address.isLinkLocalAddress()) {
+ // Linux won't let you bind a link-local address without a scope id.
+ // Find one.
+ NetworkInterface nif = NetworkInterface.getByInetAddress(address);
+ if (nif == null) {
+ throw new SocketException("Can't bind to a link-local address without a scope id: " + address);
+ }
+ try {
+ address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex());
+ } catch (UnknownHostException ex) {
+ throw new AssertionError(ex); // Can't happen.
+ }
}
}
try {
@@ -95,9 +105,9 @@ public final class IoBridge {
* Connects socket 'fd' to 'inetAddress' on 'port', with no timeout. The lack of a timeout
* means this method won't throw SocketTimeoutException.
*/
- public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException {
+ public static void connect(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException {
try {
- return IoBridge.connect(fd, inetAddress, port, 0);
+ IoBridge.connect(fd, inetAddress, port, 0);
} catch (SocketTimeoutException ex) {
throw new AssertionError(ex); // Can't happen for a connect without a timeout.
}
@@ -107,9 +117,9 @@ public final class IoBridge {
* Connects socket 'fd' to 'inetAddress' on 'port', with a the given 'timeoutMs'.
* Use timeoutMs == 0 for a blocking connect with no timeout.
*/
- public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws SocketException, SocketTimeoutException {
+ public static void connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws SocketException, SocketTimeoutException {
try {
- return connectErrno(fd, inetAddress, port, timeoutMs);
+ connectErrno(fd, inetAddress, port, timeoutMs);
} catch (ErrnoException errnoException) {
throw new ConnectException(connectDetail(inetAddress, port, timeoutMs, errnoException), errnoException);
} catch (SocketException ex) {
@@ -121,11 +131,11 @@ public final class IoBridge {
}
}
- private static boolean connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws ErrnoException, IOException {
+ private static void connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws ErrnoException, IOException {
// With no timeout, just call connect(2) directly.
if (timeoutMs == 0) {
Libcore.os.connect(fd, inetAddress, port);
- return true;
+ return;
}
// For connect with a timeout, we:
@@ -143,7 +153,7 @@ public final class IoBridge {
try {
Libcore.os.connect(fd, inetAddress, port);
IoUtils.setBlocking(fd, true); // 4. set the socket back to blocking.
- return true; // We connected immediately.
+ return; // We connected immediately.
} catch (ErrnoException errnoException) {
if (errnoException.errno != EINPROGRESS) {
throw errnoException;
@@ -160,7 +170,6 @@ public final class IoBridge {
}
} while (!IoBridge.isConnected(fd, inetAddress, port, timeoutMs, remainingTimeoutMs));
IoUtils.setBlocking(fd, true); // 4. set the socket back to blocking.
- return true; // Or we'd have thrown.
}
private static String connectDetail(InetAddress inetAddress, int port, int timeoutMs, ErrnoException cause) {
@@ -174,9 +183,15 @@ public final class IoBridge {
return detail;
}
- public static void closeSocket(FileDescriptor fd) throws IOException {
- if (!fd.valid()) {
- // Socket.close doesn't throw if you try to close an already-closed socket.
+ /**
+ * Closes the supplied file descriptor and sends a signal to any threads are currently blocking.
+ * In order for the signal to be sent the blocked threads must have registered with
+ * the AsynchronousCloseMonitor before they entered the blocking operation.
+ *
+ * <p>This method is a no-op if passed a {@code null} or already-closed file descriptor.
+ */
+ public static void closeAndSignalBlockedThreads(FileDescriptor fd) throws IOException {
+ if (fd == null || !fd.valid()) {
return;
}
int intFd = fd.getInt$();
@@ -226,6 +241,10 @@ public final class IoBridge {
// Socket options used by java.net but not exposed in SocketOptions.
public static final int JAVA_MCAST_JOIN_GROUP = 19;
public static final int JAVA_MCAST_LEAVE_GROUP = 20;
+ public static final int JAVA_MCAST_JOIN_SOURCE_GROUP = 21;
+ public static final int JAVA_MCAST_LEAVE_SOURCE_GROUP = 22;
+ public static final int JAVA_MCAST_BLOCK_SOURCE = 23;
+ public static final int JAVA_MCAST_UNBLOCK_SOURCE = 24;
public static final int JAVA_IP_MULTICAST_TTL = 17;
/**
@@ -369,16 +388,46 @@ public final class IoBridge {
return;
case IoBridge.JAVA_MCAST_JOIN_GROUP:
case IoBridge.JAVA_MCAST_LEAVE_GROUP:
+ {
StructGroupReq groupReq = (StructGroupReq) value;
int level = (groupReq.gr_group instanceof Inet4Address) ? IPPROTO_IP : IPPROTO_IPV6;
int op = (option == JAVA_MCAST_JOIN_GROUP) ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP;
Libcore.os.setsockoptGroupReq(fd, level, op, groupReq);
return;
+ }
+ case IoBridge.JAVA_MCAST_JOIN_SOURCE_GROUP:
+ case IoBridge.JAVA_MCAST_LEAVE_SOURCE_GROUP:
+ case IoBridge.JAVA_MCAST_BLOCK_SOURCE:
+ case IoBridge.JAVA_MCAST_UNBLOCK_SOURCE:
+ {
+ StructGroupSourceReq groupSourceReq = (StructGroupSourceReq) value;
+ int level = (groupSourceReq.gsr_group instanceof Inet4Address)
+ ? IPPROTO_IP : IPPROTO_IPV6;
+ int op = getGroupSourceReqOp(option);
+ Libcore.os.setsockoptGroupSourceReq(fd, level, op, groupSourceReq);
+ return;
+ }
default:
throw new SocketException("Unknown socket option: " + option);
}
}
+ private static int getGroupSourceReqOp(int javaValue) {
+ switch (javaValue) {
+ case IoBridge.JAVA_MCAST_JOIN_SOURCE_GROUP:
+ return MCAST_JOIN_SOURCE_GROUP;
+ case IoBridge.JAVA_MCAST_LEAVE_SOURCE_GROUP:
+ return MCAST_LEAVE_SOURCE_GROUP;
+ case IoBridge.JAVA_MCAST_BLOCK_SOURCE:
+ return MCAST_BLOCK_SOURCE;
+ case IoBridge.JAVA_MCAST_UNBLOCK_SOURCE:
+ return MCAST_UNBLOCK_SOURCE;
+ default:
+ throw new AssertionError(
+ "Unknown java value for setsocketopt op lookup: " + javaValue);
+ }
+ }
+
/**
* java.io only throws FileNotFoundException when opening files, regardless of what actually
* went wrong. Additionally, java.io is more restrictive than POSIX when it comes to opening
@@ -391,12 +440,10 @@ public final class IoBridge {
// On Android, we don't want default permissions to allow global access.
int mode = ((flags & O_ACCMODE) == O_RDONLY) ? 0 : 0600;
fd = Libcore.os.open(path, flags, mode);
- if (fd.valid()) {
- // Posix open(2) fails with EISDIR only if you ask for write permission.
- // Java disallows reading directories too.
- if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) {
- throw new ErrnoException("open", EISDIR);
- }
+ // Posix open(2) fails with EISDIR only if you ask for write permission.
+ // Java disallows reading directories too.
+ if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) {
+ throw new ErrnoException("open", EISDIR);
}
return fd;
} catch (ErrnoException errnoException) {
diff --git a/luni/src/main/java/libcore/io/IoUtils.java b/luni/src/main/java/libcore/io/IoUtils.java
index 10ef671..5a19f17 100644
--- a/luni/src/main/java/libcore/io/IoUtils.java
+++ b/luni/src/main/java/libcore/io/IoUtils.java
@@ -16,6 +16,8 @@
package libcore.io;
+import android.system.ErrnoException;
+import android.system.StructStat;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -25,7 +27,7 @@ import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Random;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
public final class IoUtils {
private static final Random TEMPORARY_DIRECTORY_PRNG = new Random();
diff --git a/luni/src/main/java/libcore/io/Memory.java b/luni/src/main/java/libcore/io/Memory.java
index 5743949..e148457 100644
--- a/luni/src/main/java/libcore/io/Memory.java
+++ b/luni/src/main/java/libcore/io/Memory.java
@@ -151,9 +151,33 @@ public final class Memory {
public static native void memmove(Object dstObject, int dstOffset, Object srcObject, int srcOffset, long byteCount);
public static native byte peekByte(long address);
- public static native int peekInt(long address, boolean swap);
- public static native long peekLong(long address, boolean swap);
- public static native short peekShort(long address, boolean swap);
+
+ public static int peekInt(long address, boolean swap) {
+ int result = peekIntNative(address);
+ if (swap) {
+ result = Integer.reverseBytes(result);
+ }
+ return result;
+ }
+ private static native int peekIntNative(long address);
+
+ public static long peekLong(long address, boolean swap) {
+ long result = peekLongNative(address);
+ if (swap) {
+ result = Long.reverseBytes(result);
+ }
+ return result;
+ }
+ private static native long peekLongNative(long address);
+
+ public static short peekShort(long address, boolean swap) {
+ short result = peekShortNative(address);
+ if (swap) {
+ result = Short.reverseBytes(result);
+ }
+ return result;
+ }
+ private static native short peekShortNative(long address);
public static native void peekByteArray(long address, byte[] dst, int dstOffset, int byteCount);
public static native void peekCharArray(long address, char[] dst, int dstOffset, int charCount, boolean swap);
@@ -164,9 +188,30 @@ public final class Memory {
public static native void peekShortArray(long address, short[] dst, int dstOffset, int shortCount, boolean swap);
public static native void pokeByte(long address, byte value);
- public static native void pokeInt(long address, int value, boolean swap);
- public static native void pokeLong(long address, long value, boolean swap);
- public static native void pokeShort(long address, short value, boolean swap);
+
+ public static void pokeInt(long address, int value, boolean swap) {
+ if (swap) {
+ value = Integer.reverseBytes(value);
+ }
+ pokeIntNative(address, value);
+ }
+ private static native void pokeIntNative(long address, int value);
+
+ public static void pokeLong(long address, long value, boolean swap) {
+ if (swap) {
+ value = Long.reverseBytes(value);
+ }
+ pokeLongNative(address, value);
+ }
+ private static native void pokeLongNative(long address, long value);
+
+ public static void pokeShort(long address, short value, boolean swap) {
+ if (swap) {
+ value = Short.reverseBytes(value);
+ }
+ pokeShortNative(address, value);
+ }
+ private static native void pokeShortNative(long address, short value);
public static native void pokeByteArray(long address, byte[] src, int offset, int count);
public static native void pokeCharArray(long address, char[] src, int offset, int count, boolean swap);
diff --git a/luni/src/main/java/libcore/io/MemoryMappedFile.java b/luni/src/main/java/libcore/io/MemoryMappedFile.java
index 2d8aa2b..b4cd8fc 100644
--- a/luni/src/main/java/libcore/io/MemoryMappedFile.java
+++ b/luni/src/main/java/libcore/io/MemoryMappedFile.java
@@ -16,16 +16,16 @@
package libcore.io;
+import android.system.ErrnoException;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
-import java.nio.NioUtils;
import java.nio.channels.FileChannel;
-import libcore.io.ErrnoException;
+import java.nio.NioUtils;
import libcore.io.Libcore;
import libcore.io.Memory;
-import static libcore.io.OsConstants.*;
+import static android.system.OsConstants.*;
/**
* A memory-mapped file. Use {@link #mmap} to map a file, {@link #close} to unmap a file,
diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java
index 2b68027..511bb27 100644
--- a/luni/src/main/java/libcore/io/Os.java
+++ b/luni/src/main/java/libcore/io/Os.java
@@ -16,18 +16,34 @@
package libcore.io;
+import android.system.ErrnoException;
+import android.system.GaiException;
+import android.system.StructAddrinfo;
+import android.system.StructFlock;
+import android.system.StructGroupReq;
+import android.system.StructGroupSourceReq;
+import android.system.StructLinger;
+import android.system.StructPasswd;
+import android.system.StructPollfd;
+import android.system.StructStat;
+import android.system.StructStatVfs;
+import android.system.StructTimeval;
+import android.system.StructUcred;
+import android.system.StructUtsname;
+import android.util.MutableInt;
+import android.util.MutableLong;
import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
-import libcore.util.MutableInt;
-import libcore.util.MutableLong;
public interface Os {
public FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException;
public boolean access(String path, int mode) throws ErrnoException;
+ public InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException;
public void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException;
public void chmod(String path, int mode) throws ErrnoException;
public void chown(String path, int uid, int gid) throws ErrnoException;
@@ -49,7 +65,6 @@ public interface Os {
public void fsync(FileDescriptor fd) throws ErrnoException;
public void ftruncate(FileDescriptor fd, long length) throws ErrnoException;
public String gai_strerror(int error);
- public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException;
public int getegid();
public int geteuid();
public int getgid();
@@ -77,11 +92,13 @@ public interface Os {
public boolean isatty(FileDescriptor fd);
public void kill(int pid, int signal) throws ErrnoException;
public void lchown(String path, int uid, int gid) throws ErrnoException;
+ public void link(String oldPath, String newPath) throws ErrnoException;
public void listen(FileDescriptor fd, int backlog) throws ErrnoException;
public long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException;
public StructStat lstat(String path) throws ErrnoException;
public void mincore(long address, long byteCount, byte[] vector) throws ErrnoException;
public void mkdir(String path, int mode) throws ErrnoException;
+ public void mkfifo(String path, int mode) throws ErrnoException;
public void mlock(long address, long byteCount) throws ErrnoException;
public long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException;
public void msync(long address, long byteCount, int flags) throws ErrnoException;
@@ -91,13 +108,16 @@ public interface Os {
public FileDescriptor[] pipe() throws ErrnoException;
/* TODO: if we used the non-standard ppoll(2) behind the scenes, we could take a long timeout. */
public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException;
- public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException;
- public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException;
- public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException;
- public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException;
- public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException;
- public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
- public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
+ public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException;
+ public int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException;
+ public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException;
+ public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException;
+ public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException;
+ public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException;
+ public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException;
+ public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException;
+ public String readlink(String path) throws ErrnoException;
+ public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException;
public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException;
public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException;
public void remove(String path) throws ErrnoException;
@@ -115,6 +135,7 @@ public interface Os {
public void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
public void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
public void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException;
+ public void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException;
public void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException;
public void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException;
public void setuid(int uid) throws ErrnoException;
@@ -133,7 +154,7 @@ public interface Os {
public StructUtsname uname();
public void unsetenv(String name) throws ErrnoException;
public int waitpid(int pid, MutableInt status, int options) throws ErrnoException;
- public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException;
- public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
- public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
+ public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException;
+ public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException;
+ public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException;
}
diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java
index b99941c..f5eaaa3 100644
--- a/luni/src/main/java/libcore/io/Posix.java
+++ b/luni/src/main/java/libcore/io/Posix.java
@@ -16,21 +16,37 @@
package libcore.io;
+import android.system.ErrnoException;
+import android.system.GaiException;
+import android.system.StructAddrinfo;
+import android.system.StructFlock;
+import android.system.StructGroupReq;
+import android.system.StructGroupSourceReq;
+import android.system.StructLinger;
+import android.system.StructPasswd;
+import android.system.StructPollfd;
+import android.system.StructStat;
+import android.system.StructStatVfs;
+import android.system.StructTimeval;
+import android.system.StructUcred;
+import android.system.StructUtsname;
+import android.util.MutableInt;
+import android.util.MutableLong;
import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.NioUtils;
-import libcore.util.MutableInt;
-import libcore.util.MutableLong;
public final class Posix implements Os {
Posix() { }
public native FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException;
public native boolean access(String path, int mode) throws ErrnoException;
+ public native InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException;
public native void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException;
public native void chmod(String path, int mode) throws ErrnoException;
public native void chown(String path, int uid, int gid) throws ErrnoException;
@@ -52,7 +68,6 @@ public final class Posix implements Os {
public native void fsync(FileDescriptor fd) throws ErrnoException;
public native void ftruncate(FileDescriptor fd, long length) throws ErrnoException;
public native String gai_strerror(int error);
- public native InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException;
public native int getegid();
public native int geteuid();
public native int getgid();
@@ -79,11 +94,13 @@ public final class Posix implements Os {
public native boolean isatty(FileDescriptor fd);
public native void kill(int pid, int signal) throws ErrnoException;
public native void lchown(String path, int uid, int gid) throws ErrnoException;
+ public native void link(String oldPath, String newPath) throws ErrnoException;
public native void listen(FileDescriptor fd, int backlog) throws ErrnoException;
public native long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException;
public native StructStat lstat(String path) throws ErrnoException;
public native void mincore(long address, long byteCount, byte[] vector) throws ErrnoException;
public native void mkdir(String path, int mode) throws ErrnoException;
+ public native void mkfifo(String path, int mode) throws ErrnoException;
public native void mlock(long address, long byteCount) throws ErrnoException;
public native long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException;
public native void msync(long address, long byteCount, int flags) throws ErrnoException;
@@ -92,43 +109,46 @@ public final class Posix implements Os {
public native FileDescriptor open(String path, int flags, int mode) throws ErrnoException;
public native FileDescriptor[] pipe() throws ErrnoException;
public native int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException;
- public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException {
+ public native void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException;
+ public native int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException;
+ public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException {
if (buffer.isDirect()) {
return preadBytes(fd, buffer, buffer.position(), buffer.remaining(), offset);
} else {
return preadBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), offset);
}
}
- public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException {
+ public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException {
// This indirection isn't strictly necessary, but ensures that our public interface is type safe.
return preadBytes(fd, bytes, byteOffset, byteCount, offset);
}
- private native int preadBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException;
- public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException {
+ private native int preadBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException;
+ public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException {
if (buffer.isDirect()) {
return pwriteBytes(fd, buffer, buffer.position(), buffer.remaining(), offset);
} else {
return pwriteBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), offset);
}
}
- public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException {
+ public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException {
// This indirection isn't strictly necessary, but ensures that our public interface is type safe.
return pwriteBytes(fd, bytes, byteOffset, byteCount, offset);
}
- private native int pwriteBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException;
- public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
+ private native int pwriteBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException;
+ public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
if (buffer.isDirect()) {
return readBytes(fd, buffer, buffer.position(), buffer.remaining());
} else {
return readBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
}
}
- public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+ public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException {
// This indirection isn't strictly necessary, but ensures that our public interface is type safe.
return readBytes(fd, bytes, byteOffset, byteCount);
}
- private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException;
- public native int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
+ private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException, InterruptedIOException;
+ public native String readlink(String path) throws ErrnoException;
+ public native int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException;
public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException {
if (buffer.isDirect()) {
return recvfromBytes(fd, buffer, buffer.position(), buffer.remaining(), flags, srcAddress);
@@ -166,6 +186,7 @@ public final class Posix implements Os {
public native void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
public native void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
public native void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException;
+ public native void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException;
public native void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException;
public native void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException;
public native void setuid(int uid) throws ErrnoException;
@@ -190,17 +211,17 @@ public final class Posix implements Os {
public native StructUtsname uname();
public native void unsetenv(String name) throws ErrnoException;
public native int waitpid(int pid, MutableInt status, int options) throws ErrnoException;
- public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
+ public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
if (buffer.isDirect()) {
return writeBytes(fd, buffer, buffer.position(), buffer.remaining());
} else {
return writeBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
}
}
- public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+ public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException {
// This indirection isn't strictly necessary, but ensures that our public interface is type safe.
return writeBytes(fd, bytes, byteOffset, byteCount);
}
- private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException;
- public native int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
+ private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException, InterruptedIOException;
+ public native int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException;
}
diff --git a/luni/src/main/java/libcore/io/Streams.java b/luni/src/main/java/libcore/io/Streams.java
index cbad4a4..1f78edd 100644
--- a/luni/src/main/java/libcore/io/Streams.java
+++ b/luni/src/main/java/libcore/io/Streams.java
@@ -135,8 +135,9 @@ public final class Streams {
}
/**
- * Call {@code in.read()} repeatedly until either the stream is exhausted or
- * {@code byteCount} bytes have been read.
+ * Skip <b>at most</b> {@code byteCount} bytes from {@code in} by calling read
+ * repeatedly until either the stream is exhausted or we read fewer bytes than
+ * we ask for.
*
* <p>This method reuses the skip buffer but is careful to never use it at
* the same time that another stream is using it. Otherwise streams that use
diff --git a/luni/src/main/java/libcore/io/StructPasswd.java b/luni/src/main/java/libcore/io/StructPasswd.java
deleted file mode 100644
index 6f5e058..0000000
--- a/luni/src/main/java/libcore/io/StructPasswd.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-/**
- * Information returned by getpwnam(3) and getpwuid(3). Corresponds to C's
- * {@code struct passwd} from
- * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pwd.h.html">&lt;pwd.h&gt;</a>
- */
-public final class StructPasswd {
- public String pw_name;
- public int pw_uid; /* uid_t */
- public int pw_gid; /* gid_t */
- public String pw_dir;
- public String pw_shell;
-
- public StructPasswd(String pw_name, int pw_uid, int pw_gid, String pw_dir, String pw_shell) {
- this.pw_name = pw_name;
- this.pw_uid = pw_uid;
- this.pw_gid = pw_gid;
- this.pw_dir = pw_dir;
- this.pw_shell = pw_shell;
- }
-}
diff --git a/luni/src/main/java/libcore/io/StructPollfd.java b/luni/src/main/java/libcore/io/StructPollfd.java
deleted file mode 100644
index c659d6e..0000000
--- a/luni/src/main/java/libcore/io/StructPollfd.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-import java.io.FileDescriptor;
-
-/**
- * Corresponds to C's {@code struct pollfd} from
- * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/poll.h.html">&lt;poll.h&gt;</a>
- */
-public final class StructPollfd {
- /** The file descriptor to poll. */
- public FileDescriptor fd;
-
- /**
- * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
- * POLLOUT to the write fd set.
- */
- public short events;
-
- /** The events that actually happened. */
- public short revents;
-
- /**
- * A non-standard extension that lets callers conveniently map back to the object
- * their fd belongs to. This is used by Selector, for example, to associate each
- * FileDescriptor with the corresponding SelectionKey.
- */
- public Object userData;
-
- @Override public String toString() {
- return "StructPollfd[fd=" + fd + ",events=" + events + ",revents=" + revents + "]";
- }
-}
diff --git a/luni/src/main/java/libcore/io/StructStat.java b/luni/src/main/java/libcore/io/StructStat.java
deleted file mode 100644
index 05ecca7..0000000
--- a/luni/src/main/java/libcore/io/StructStat.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-/**
- * File information returned by fstat(2), lstat(2), and stat(2). Corresponds to C's
- * {@code struct stat} from
- * <a href="http://www.opengroup.org/onlinepubs/000095399/basedefs/sys/stat.h.html">&lt;stat.h&gt;</a>
- */
-public final class StructStat {
- /** Device ID of device containing file. */
- public final long st_dev; /*dev_t*/
-
- /** File serial number (inode). */
- public final long st_ino; /*ino_t*/
-
- /** Mode (permissions) of file. */
- public final int st_mode; /*mode_t*/
-
- /** Number of hard links to the file. */
- public final long st_nlink; /*nlink_t*/
-
- /** User ID of file. */
- public final int st_uid; /*uid_t*/
-
- /** Group ID of file. */
- public final int st_gid; /*gid_t*/
-
- /** Device ID (if file is character or block special). */
- public final long st_rdev; /*dev_t*/
-
- /**
- * For regular files, the file size in bytes.
- * For symbolic links, the length in bytes of the pathname contained in the symbolic link.
- * For a shared memory object, the length in bytes.
- * For a typed memory object, the length in bytes.
- * For other file types, the use of this field is unspecified.
- */
- public final long st_size; /*off_t*/
-
- /** Time of last access. */
- public final long st_atime; /*time_t*/
-
- /** Time of last data modification. */
- public final long st_mtime; /*time_t*/
-
- /** Time of last status change. */
- public final long st_ctime; /*time_t*/
-
- /**
- * A file system-specific preferred I/O block size for this object.
- * For some file system types, this may vary from file to file.
- */
- public final long st_blksize; /*blksize_t*/
-
- /** Number of blocks allocated for this object. */
- public final long st_blocks; /*blkcnt_t*/
-
- StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
- long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime,
- long st_blksize, long st_blocks) {
- this.st_dev = st_dev;
- this.st_ino = st_ino;
- this.st_mode = st_mode;
- this.st_nlink = st_nlink;
- this.st_uid = st_uid;
- this.st_gid = st_gid;
- this.st_rdev = st_rdev;
- this.st_size = st_size;
- this.st_atime = st_atime;
- this.st_mtime = st_mtime;
- this.st_ctime = st_ctime;
- this.st_blksize = st_blksize;
- this.st_blocks = st_blocks;
- }
-}
diff --git a/luni/src/main/java/libcore/io/StructUtsname.java b/luni/src/main/java/libcore/io/StructUtsname.java
deleted file mode 100644
index e6a8e42..0000000
--- a/luni/src/main/java/libcore/io/StructUtsname.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-/**
- * Information returned by uname(2). Corresponds to C's
- * {@code struct utsname} from
- * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_utsname.h.html">&lt;sys/utsname.h&gt;</a>
- */
-public final class StructUtsname {
- /** The OS name, such as "Linux". */
- public final String sysname;
-
- /** The machine's unqualified name on some implementation-defined network. */
- public final String nodename;
-
- /** The OS release, such as "2.6.35-27-generic". */
- public final String release;
-
- /** The OS version, such as "#48-Ubuntu SMP Tue Feb 22 20:25:29 UTC 2011". */
- public final String version;
-
- /** The machine architecture, such as "armv7l" or "x86_64". */
- public final String machine;
-
- StructUtsname(String sysname, String nodename, String release, String version, String machine) {
- this.sysname = sysname;
- this.nodename = nodename;
- this.release = release;
- this.version = version;
- this.machine = machine;
- }
-}
diff --git a/luni/src/main/java/libcore/net/MimeUtils.java b/luni/src/main/java/libcore/net/MimeUtils.java
index aaa5670..a5a1469 100644
--- a/luni/src/main/java/libcore/net/MimeUtils.java
+++ b/luni/src/main/java/libcore/net/MimeUtils.java
@@ -49,14 +49,13 @@ public final class MimeUtils {
add("application/andrew-inset", "ez");
add("application/dsptype", "tsp");
- add("application/futuresplash", "spl");
add("application/hta", "hta");
add("application/mac-binhex40", "hqx");
- add("application/mac-compactpro", "cpt");
add("application/mathematica", "nb");
add("application/msaccess", "mdb");
add("application/oda", "oda");
add("application/ogg", "ogg");
+ add("application/ogg", "oga");
add("application/pdf", "pdf");
add("application/pgp-keys", "key");
add("application/pgp-signature", "pgp");
@@ -133,14 +132,15 @@ public final class MimeUtils {
add("application/x-dms", "dms");
add("application/x-doom", "wad");
add("application/x-dvi", "dvi");
- add("application/x-flac", "flac");
add("application/x-font", "pfa");
add("application/x-font", "pfb");
add("application/x-font", "gsf");
add("application/x-font", "pcf");
add("application/x-font", "pcf.Z");
add("application/x-freemind", "mm");
+ // application/futuresplash isn't IANA, so application/x-futuresplash should come first.
add("application/x-futuresplash", "spl");
+ add("application/futuresplash", "spl");
add("application/x-gnumeric", "gnumeric");
add("application/x-go-sgf", "sgf");
add("application/x-graphing-calculator", "gcf");
@@ -212,11 +212,19 @@ public final class MimeUtils {
add("application/x-xfig", "fig");
add("application/xhtml+xml", "xhtml");
add("audio/3gpp", "3gpp");
+ add("audio/aac", "aac");
+ add("audio/aac-adts", "aac");
add("audio/amr", "amr");
+ add("audio/amr-wb", "awb");
add("audio/basic", "snd");
+ add("audio/flac", "flac");
+ add("application/x-flac", "flac");
+ add("audio/imelody", "imy");
add("audio/midi", "mid");
add("audio/midi", "midi");
+ add("audio/midi", "ota");
add("audio/midi", "kar");
+ add("audio/midi", "rtttl");
add("audio/midi", "xmf");
add("audio/mobile-xmf", "mxmf");
// add ".mp3" first so it will be the default for guessExtensionFromMimeType
@@ -231,6 +239,7 @@ public final class MimeUtils {
add("audio/x-aiff", "aiff");
add("audio/x-aiff", "aifc");
add("audio/x-gsm", "gsm");
+ add("audio/x-matroska", "mka");
add("audio/x-mpegurl", "m3u");
add("audio/x-ms-wma", "wma");
add("audio/x-ms-wax", "wax");
@@ -241,8 +250,12 @@ public final class MimeUtils {
add("audio/x-scpls", "pls");
add("audio/x-sd2", "sd2");
add("audio/x-wav", "wav");
+ // image/bmp isn't IANA, so image/x-ms-bmp should come first.
+ add("image/x-ms-bmp", "bmp");
add("image/bmp", "bmp");
add("image/gif", "gif");
+ // image/ico isn't IANA, so image/x-icon should come first.
+ add("image/x-icon", "ico");
add("image/ico", "cur");
add("image/ico", "ico");
add("image/ief", "ief");
@@ -258,15 +271,14 @@ public final class MimeUtils {
add("image/vnd.djvu", "djvu");
add("image/vnd.djvu", "djv");
add("image/vnd.wap.wbmp", "wbmp");
+ add("image/webp", "webp");
add("image/x-cmu-raster", "ras");
add("image/x-coreldraw", "cdr");
add("image/x-coreldrawpattern", "pat");
add("image/x-coreldrawtemplate", "cdt");
add("image/x-corelphotopaint", "cpt");
- add("image/x-icon", "ico");
add("image/x-jg", "art");
add("image/x-jng", "jng");
- add("image/x-ms-bmp", "bmp");
add("image/x-photoshop", "psd");
add("image/x-portable-anymap", "pnm");
add("image/x-portable-bitmap", "pbm");
@@ -298,7 +310,6 @@ public final class MimeUtils {
add("text/plain", "po"); // reserve "pot" for vnd.ms-powerpoint
add("text/richtext", "rtx");
add("text/rtf", "rtf");
- add("text/texmacs", "ts");
add("text/text", "phps");
add("text/tab-separated-values", "tsv");
add("text/xml", "xml");
@@ -334,12 +345,15 @@ public final class MimeUtils {
add("text/x-vcard", "vcf");
add("video/3gpp", "3gpp");
add("video/3gpp", "3gp");
- add("video/3gpp", "3g2");
+ add("video/3gpp2", "3gpp2");
+ add("video/3gpp2", "3g2");
+ add("video/avi", "avi");
add("video/dl", "dl");
add("video/dv", "dif");
add("video/dv", "dv");
add("video/fli", "fli");
add("video/m4v", "m4v");
+ add("video/mp2ts", "ts");
add("video/mpeg", "mpeg");
add("video/mpeg", "mpg");
add("video/mpeg", "mpe");
@@ -348,8 +362,10 @@ public final class MimeUtils {
add("video/quicktime", "qt");
add("video/quicktime", "mov");
add("video/vnd.mpegurl", "mxu");
+ add("video/webm", "webm");
add("video/x-la-asf", "lsf");
add("video/x-la-asf", "lsx");
+ add("video/x-matroska", "mkv");
add("video/x-mng", "mng");
add("video/x-ms-asf", "asf");
add("video/x-ms-asf", "asx");
@@ -357,7 +373,6 @@ public final class MimeUtils {
add("video/x-ms-wmv", "wmv");
add("video/x-ms-wmx", "wmx");
add("video/x-ms-wvx", "wvx");
- add("video/x-msvideo", "avi");
add("video/x-sgi-movie", "movie");
add("video/x-webex", "wrf");
add("x-conference/x-cooltalk", "ice");
@@ -366,18 +381,17 @@ public final class MimeUtils {
}
private static void add(String mimeType, String extension) {
- //
- // if we have an existing x --> y mapping, we do not want to
- // override it with another mapping x --> ?
- // this is mostly because of the way the mime-type map below
- // is constructed (if a mime type maps to several extensions
- // the first extension is considered the most popular and is
- // added first; we do not want to overwrite it later).
- //
+ // If we have an existing x -> y mapping, we do not want to
+ // override it with another mapping x -> y2.
+ // If a mime type maps to several extensions
+ // the first extension added is considered the most popular
+ // so we do not want to overwrite it later.
if (!mimeTypeToExtensionMap.containsKey(mimeType)) {
mimeTypeToExtensionMap.put(mimeType, extension);
}
- extensionToMimeTypeMap.put(extension, mimeType);
+ if (!extensionToMimeTypeMap.containsKey(extension)) {
+ extensionToMimeTypeMap.put(extension, mimeType);
+ }
}
private static InputStream getContentTypesPropertiesStream() {
diff --git a/luni/src/main/java/libcore/net/RawSocket.java b/luni/src/main/java/libcore/net/RawSocket.java
deleted file mode 100644
index 08a7d09..0000000
--- a/luni/src/main/java/libcore/net/RawSocket.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.net;
-
-import dalvik.system.CloseGuard;
-import java.io.Closeable;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.SocketException;
-import java.util.Arrays;
-import libcore.io.IoBridge;
-
-/**
- * This class allows raw L2 packets to be sent and received via the
- * specified network interface. The receive-side implementation is
- * restricted to UDP packets for efficiency.
- *
- * @hide
- */
-public class RawSocket implements Closeable {
- /**
- * Ethernet IP protocol type, part of the L2 header of IP packets.
- */
- public static final short ETH_P_IP = (short) 0x0800;
-
- /**
- * Ethernet ARP protocol type, part of the L2 header of ARP packets.
- */
- public static final short ETH_P_ARP = (short) 0x0806;
-
- private static native void create(FileDescriptor fd, short
- protocolType, String interfaceName)
- throws SocketException;
- private static native int sendPacket(FileDescriptor fd,
- String interfaceName, short protocolType, byte[] destMac, byte[] packet,
- int offset, int byteCount);
- private static native int recvPacket(FileDescriptor fd, byte[] packet,
- int offset, int byteCount, int destPort, int timeoutMillis);
-
- private final FileDescriptor fd;
- private final String mInterfaceName;
- private final short mProtocolType;
- private final CloseGuard guard = CloseGuard.get();
-
- /**
- * Creates a socket on the specified interface.
- */
- public RawSocket(String interfaceName, short protocolType)
- throws SocketException {
- mInterfaceName = interfaceName;
- mProtocolType = protocolType;
- fd = new FileDescriptor();
- create(fd, mProtocolType, mInterfaceName);
- guard.open("close");
- }
-
- /**
- * Reads a raw packet into the specified buffer, with the
- * specified timeout. If the destPort is -1, then the IP
- * destination port is not verified, otherwise only packets
- * destined for the specified UDP port are returned. Returns the
- * length actually read. No indication of overflow is signaled.
- * The packet data will start at the IP header (EthernetII
- * dest/source/type headers are removed).
- */
- public int read(byte[] packet, int offset, int byteCount, int destPort,
- int timeoutMillis) {
- if (packet == null) {
- throw new NullPointerException("packet == null");
- }
-
- Arrays.checkOffsetAndCount(packet.length, offset, byteCount);
-
- if (destPort > 65535) {
- throw new IllegalArgumentException("Port out of range: "
- + destPort);
- }
-
- return recvPacket(fd, packet, offset, byteCount, destPort,
- timeoutMillis);
- }
-
- /**
- * Writes a raw packet to the desired interface. A L2 header will
- * be added which includes the specified destination address, our
- * source MAC, and the specified protocol type. The caller is responsible
- * for computing correct IP-header and payload checksums.
- */
- public int write(byte[] destMac, byte[] packet, int offset, int byteCount) {
- if (destMac == null) {
- throw new NullPointerException("destMac == null");
- }
-
- if (packet == null) {
- throw new NullPointerException("packet == null");
- }
-
- Arrays.checkOffsetAndCount(packet.length, offset, byteCount);
-
- if (destMac.length != 6) {
- throw new IllegalArgumentException("MAC length must be 6: "
- + destMac.length);
- }
-
- return sendPacket(fd, mInterfaceName, mProtocolType, destMac, packet,
- offset, byteCount);
- }
-
- /**
- * Closes the socket. After this method is invoked, subsequent
- * read/write operations will fail.
- */
- public void close() throws IOException {
- guard.close();
- IoBridge.closeSocket(fd);
- }
-
- @Override protected void finalize() throws Throwable {
- try {
- if (guard != null) {
- guard.warnIfOpen();
- }
- close();
- } finally {
- super.finalize();
- }
- }
-}
diff --git a/luni/src/main/java/libcore/net/event/NetworkEventDispatcher.java b/luni/src/main/java/libcore/net/event/NetworkEventDispatcher.java
new file mode 100644
index 0000000..d1c7c21
--- /dev/null
+++ b/luni/src/main/java/libcore/net/event/NetworkEventDispatcher.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.net.event;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A singleton used to dispatch network events to registered listeners.
+ */
+public class NetworkEventDispatcher {
+
+ private static final NetworkEventDispatcher instance = new NetworkEventDispatcher();
+
+ private final List<NetworkEventListener> listeners =
+ new CopyOnWriteArrayList<NetworkEventListener>();
+
+ /**
+ * Returns the shared {@link NetworkEventDispatcher} instance.
+ */
+ public static NetworkEventDispatcher getInstance() {
+ return instance;
+ }
+
+ /** Visible for testing. Use {@link #getInstance()} instead. */
+ protected NetworkEventDispatcher() {
+ }
+
+ /**
+ * Registers a listener to be notified when network events occur.
+ * It can be deregistered using {@link #removeListener(NetworkEventListener)}
+ */
+ public void addListener(NetworkEventListener toAdd) {
+ if (toAdd == null) {
+ throw new NullPointerException("toAdd == null");
+ }
+ listeners.add(toAdd);
+ }
+
+ /**
+ * De-registers a listener previously added with {@link #addListener(NetworkEventListener)}. If
+ * the listener was not previously registered this is a no-op.
+ */
+ public void removeListener(NetworkEventListener toRemove) {
+ for (NetworkEventListener listener : listeners) {
+ if (listener == toRemove) {
+ listeners.remove(listener);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Notifies registered listeners of a network configuration change.
+ */
+ public void onNetworkConfigurationChanged() {
+ for (NetworkEventListener listener : listeners) {
+ try {
+ listener.onNetworkConfigurationChanged();
+ } catch (RuntimeException e) {
+ System.logI("Exception thrown during network event propagation", e);
+ }
+ }
+ }
+}
diff --git a/luni/src/main/java/libcore/net/event/NetworkEventListener.java b/luni/src/main/java/libcore/net/event/NetworkEventListener.java
new file mode 100644
index 0000000..73b9f88
--- /dev/null
+++ b/luni/src/main/java/libcore/net/event/NetworkEventListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.net.event;
+
+/**
+ * A base class for objects interested in network events.
+ */
+public class NetworkEventListener {
+
+ public void onNetworkConfigurationChanged() {
+ // no-op
+ }
+}
diff --git a/luni/src/main/java/libcore/net/url/FileURLConnection.java b/luni/src/main/java/libcore/net/url/FileURLConnection.java
index b4654cd..43eaa7d 100644
--- a/luni/src/main/java/libcore/net/url/FileURLConnection.java
+++ b/luni/src/main/java/libcore/net/url/FileURLConnection.java
@@ -28,6 +28,11 @@ import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLConnection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
import libcore.net.UriCodec;
/**
@@ -38,17 +43,45 @@ import libcore.net.UriCodec;
*/
public class FileURLConnection extends URLConnection {
+ private static final Comparator<String> HEADER_COMPARATOR = new Comparator<String>() {
+ @Override
+ public int compare(String a, String b) {
+ if (a == b) {
+ return 0;
+ } else if (a == null) {
+ return -1;
+ } else if (b == null) {
+ return 1;
+ } else {
+ return String.CASE_INSENSITIVE_ORDER.compare(a, b);
+ }
+ }
+ };
+
private String filename;
private InputStream is;
- private int length = -1;
+ private long length = -1;
+
+ private long lastModified = -1;
private boolean isDir;
private FilePermission permission;
/**
+ * A set of three key value pairs representing the headers we support.
+ */
+ private final String[] headerKeysAndValues;
+
+ private static final int CONTENT_TYPE_VALUE_IDX = 1;
+ private static final int CONTENT_LENGTH_VALUE_IDX = 3;
+ private static final int LAST_MODIFIED_VALUE_IDX = 5;
+
+ private Map<String, List<String>> headerFields;
+
+ /**
* Creates an instance of <code>FileURLConnection</code> for establishing
* a connection to the file pointed by this <code>URL<code>
*
@@ -61,6 +94,10 @@ public class FileURLConnection extends URLConnection {
filename = "";
}
filename = UriCodec.decode(filename);
+ headerKeysAndValues = new String[] {
+ "content-type", null,
+ "content-length", null,
+ "last-modified", null };
}
/**
@@ -74,27 +111,122 @@ public class FileURLConnection extends URLConnection {
@Override
public void connect() throws IOException {
File f = new File(filename);
+ IOException error = null;
if (f.isDirectory()) {
isDir = true;
is = getDirectoryListing(f);
// use -1 for the contentLength
+ lastModified = f.lastModified();
+ headerKeysAndValues[CONTENT_TYPE_VALUE_IDX] = "text/html";
} else {
- is = new BufferedInputStream(new FileInputStream(f));
- long lengthAsLong = f.length();
- length = lengthAsLong <= Integer.MAX_VALUE ? (int) lengthAsLong : Integer.MAX_VALUE;
+ try {
+ is = new BufferedInputStream(new FileInputStream(f));
+ } catch (IOException ioe) {
+ error = ioe;
+ }
+
+ if (error == null) {
+ length = f.length();
+ lastModified = f.lastModified();
+ headerKeysAndValues[CONTENT_TYPE_VALUE_IDX] = getContentTypeForPlainFiles();
+ } else {
+ headerKeysAndValues[CONTENT_TYPE_VALUE_IDX] = "content/unknown";
+ }
}
+
+ headerKeysAndValues[CONTENT_LENGTH_VALUE_IDX] = String.valueOf(length);
+ headerKeysAndValues[LAST_MODIFIED_VALUE_IDX] = String.valueOf(lastModified);
+
connected = true;
+ if (error != null) {
+ throw error;
+ }
+ }
+
+ @Override
+ public String getHeaderField(String key) {
+ if (!connected) {
+ try {
+ connect();
+ } catch (IOException ioe) {
+ return null;
+ }
+ }
+
+ for (int i = 0; i < headerKeysAndValues.length; i += 2) {
+ if (headerKeysAndValues[i].equalsIgnoreCase(key)) {
+ return headerKeysAndValues[i + 1];
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getHeaderFieldKey(int position) {
+ if (!connected) {
+ try {
+ connect();
+ } catch (IOException ioe) {
+ return null;
+ }
+ }
+
+ if (position < 0 || position > headerKeysAndValues.length / 2) {
+ return null;
+ }
+
+ return headerKeysAndValues[position * 2];
+ }
+
+ @Override
+ public String getHeaderField(int position) {
+ if (!connected) {
+ try {
+ connect();
+ } catch (IOException ioe) {
+ return null;
+ }
+ }
+
+ if (position < 0 || position > headerKeysAndValues.length / 2) {
+ return null;
+ }
+
+ return headerKeysAndValues[(position * 2) + 1];
+ }
+
+ @Override
+ public Map<String, List<String>> getHeaderFields() {
+ if (headerFields == null) {
+ final TreeMap<String, List<String>> headerFieldsMap = new TreeMap<>(HEADER_COMPARATOR);
+
+ for (int i = 0; i < headerKeysAndValues.length; i+=2) {
+ headerFieldsMap.put(headerKeysAndValues[i],
+ Collections.singletonList(headerKeysAndValues[i + 1]));
+ }
+
+ headerFields = Collections.unmodifiableMap(headerFieldsMap);
+ }
+
+ return headerFields;
}
/**
- * Returns the length of the file in bytes.
- *
- * @return the length of the file
- *
- * @see #getContentType()
+ * Returns the length of the file in bytes, or {@code -1} if the length cannot be
+ * represented as an {@code int}. See {@link #getContentLengthLong()} for a method that can
+ * handle larger files.
*/
@Override
public int getContentLength() {
+ long length = getContentLengthLong();
+ return length <= Integer.MAX_VALUE ? (int) length : -1;
+ }
+
+ /**
+ * Returns the length of the file in bytes.
+ */
+ private long getContentLengthLong() {
try {
if (!connected) {
connect();
@@ -113,16 +245,11 @@ public class FileURLConnection extends URLConnection {
*/
@Override
public String getContentType() {
- try {
- if (!connected) {
- connect();
- }
- } catch (IOException e) {
- return "content/unknown";
- }
- if (isDir) {
- return "text/plain";
- }
+ // The content-type header field is always at position 0.
+ return getHeaderField(0);
+ }
+
+ private String getContentTypeForPlainFiles() {
String result = guessContentTypeFromName(url.getFile());
if (result != null) {
return result;
diff --git a/luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java b/luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java
index 762f0e2..b01a20a 100644
--- a/luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java
+++ b/luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java
@@ -258,12 +258,10 @@ public class JarURLConnectionImpl extends JarURLConnection {
}
/**
- * Returns the content length of the resource. Test cases reveal that if the
- * URL is referring to a Jar file, this method answers a content-length
- * returned by URLConnection. For jar entry it should return it's size.
- * Otherwise, it will return -1.
- *
- * @return the content length
+ * Returns the content length of the resource. Test cases reveal that if the URL is referring to
+ * a Jar file, this method answers a content-length returned by URLConnection. For a jar entry
+ * it returns the entry's size if it can be represented as an {@code int}. Otherwise, it will
+ * return -1.
*/
@Override
public int getContentLength() {
diff --git a/luni/src/main/java/libcore/reflect/AnnotationAccess.java b/luni/src/main/java/libcore/reflect/AnnotationAccess.java
index 4e34284..2a72c18 100644
--- a/luni/src/main/java/libcore/reflect/AnnotationAccess.java
+++ b/luni/src/main/java/libcore/reflect/AnnotationAccess.java
@@ -16,7 +16,6 @@
package libcore.reflect;
-import com.android.dex.ClassDef;
import com.android.dex.Dex;
import com.android.dex.EncodedValueReader;
import com.android.dex.FieldId;
@@ -167,7 +166,7 @@ public final class AnnotationAccess {
*/
public static <A extends Annotation> A getDeclaredAnnotation(
AnnotatedElement element, Class<A> annotationClass) {
- com.android.dex.Annotation a = getMethodAnnotation(element, annotationClass);
+ com.android.dex.Annotation a = getAnnotation(element, annotationClass);
return a != null
? toAnnotationInstance(getDexClass(element), annotationClass, a)
: null;
@@ -178,29 +177,29 @@ public final class AnnotationAccess {
*/
public static boolean isDeclaredAnnotationPresent(
AnnotatedElement element, Class<? extends Annotation> annotationClass) {
- return getMethodAnnotation(element, annotationClass) != null;
+ return getAnnotation(element, annotationClass) != null;
}
- private static com.android.dex.Annotation getMethodAnnotation(
+ private static com.android.dex.Annotation getAnnotation(
AnnotatedElement element, Class<? extends Annotation> annotationClass) {
- Class<?> dexClass = getDexClass(element);
- Dex dex = dexClass.getDex();
- int annotationTypeIndex = getTypeIndex(dex, annotationClass);
- if (annotationTypeIndex == -1) {
- return null; // The dex file doesn't use this annotation.
- }
-
int annotationSetOffset = getAnnotationSetOffset(element);
if (annotationSetOffset == 0) {
return null; // no annotation
}
+ Class<?> dexClass = getDexClass(element);
+ Dex dex = dexClass.getDex();
Dex.Section setIn = dex.open(annotationSetOffset); // annotation_set_item
+ String annotationInternalName = InternalNames.getInternalName(annotationClass);
for (int i = 0, size = setIn.readInt(); i < size; i++) {
int annotationOffset = setIn.readInt();
Dex.Section annotationIn = dex.open(annotationOffset); // annotation_item
+ // The internal string name of the annotation is compared here and deliberately not
+ // the value of annotationClass.getTypeIndex(). The annotationClass may have been
+ // defined by a different dex file, which would make the indexes incomparable.
com.android.dex.Annotation candidate = annotationIn.readAnnotation();
- if (candidate.getTypeIndex() == annotationTypeIndex) {
+ String candidateInternalName = dex.typeNames().get(candidate.getTypeIndex());
+ if (candidateInternalName.equals(annotationInternalName)) {
return candidate;
}
}
@@ -228,19 +227,22 @@ public final class AnnotationAccess {
int methodsSize = directoryIn.readInt();
directoryIn.readInt(); // parameters size
- int fieldIndex = element instanceof Field ? ((Field) element).getDexFieldIndex() : -1;
- for (int i = 0; i < fieldsSize; i++) {
- int candidateFieldIndex = directoryIn.readInt();
- int annotationSetOffset = directoryIn.readInt();
- if (candidateFieldIndex == fieldIndex) {
- return annotationSetOffset;
- }
- }
- // we must read all fields prior to methods, if we were searching for a field then we missed
if (element instanceof Field) {
+ int fieldIndex = ((Field) element).getDexFieldIndex();
+ for (int i = 0; i < fieldsSize; i++) {
+ int candidateFieldIndex = directoryIn.readInt();
+ int annotationSetOffset = directoryIn.readInt();
+ if (candidateFieldIndex == fieldIndex) {
+ return annotationSetOffset;
+ }
+ }
+ // if we were searching for a field then we missed
return 0;
}
+ // Skip through the fields without reading them and look for constructors or methods.
+ directoryIn.skip(8 * fieldsSize);
+
int methodIndex= element instanceof Method ? ((Method) element).getDexMethodIndex()
: ((Constructor<?>) element).getDexMethodIndex();
for (int i = 0; i < methodsSize; i++) {
@@ -265,23 +267,6 @@ public final class AnnotationAccess {
: ((Member) element).getDeclaringClass();
}
- public static int getFieldIndex(Class<?> declaringClass, Class<?> type, String name) {
- Dex dex = declaringClass.getDex();
- int declaringClassIndex = getTypeIndex(dex, declaringClass);
- int typeIndex = getTypeIndex(dex, type);
- int nameIndex = dex.findStringIndex(name);
- FieldId fieldId = new FieldId(dex, declaringClassIndex, typeIndex, nameIndex);
- return dex.findFieldIndex(fieldId);
- }
-
- public static int getMethodIndex(Class<?> declaringClass, String name, int protoIndex) {
- Dex dex = declaringClass.getDex();
- int declaringClassIndex = getTypeIndex(dex, declaringClass);
- int nameIndex = dex.findStringIndex(name);
- MethodId methodId = new MethodId(dex, declaringClassIndex, protoIndex, nameIndex);
- return dex.findMethodIndex(methodId);
- }
-
/**
* Returns the parameter annotations on {@code member}.
*/
@@ -354,6 +339,8 @@ public final class AnnotationAccess {
*/
Class<?> annotationClass = method.getDeclaringClass();
+ // All lookups of type and string indexes are within the Dex that declares the annotation so
+ // the indexes can be compared directly.
Dex dex = annotationClass.getDex();
EncodedValueReader reader = getOnlyAnnotationValue(
dex, annotationClass, "Ldalvik/annotation/AnnotationDefault;");
@@ -362,7 +349,7 @@ public final class AnnotationAccess {
}
int fieldCount = reader.readAnnotation();
- if (reader.getAnnotationType() != getTypeIndex(dex, annotationClass)) {
+ if (reader.getAnnotationType() != annotationClass.getDexTypeIndex()) {
throw new AssertionError("annotation value type != annotation class");
}
@@ -384,7 +371,7 @@ public final class AnnotationAccess {
* Returns the class of which {@code c} is a direct member. If {@code c} is
* defined in a method or constructor, this is not transitive.
*/
- public static Class<?> getDeclaringClass(Class<?> c) {
+ public static Class<?> getEnclosingClass(Class<?> c) {
/*
* public class Bar {
* @EnclosingClass(value=Bar)
@@ -537,22 +524,6 @@ public final class AnnotationAccess {
* was derived.
*/
- /** Find dex's type index for the class c */
- private static int getTypeIndex(Dex dex, Class<?> c) {
- if (dex == c.getDex()) {
- return c.getDexTypeIndex();
- }
- if (dex == null) {
- return -1;
- }
- int typeIndex = dex.findTypeIndex(InternalNames.getInternalName(c));
- if (typeIndex < 0) {
- typeIndex = -1;
- }
- return typeIndex;
- }
-
-
private static EncodedValueReader getAnnotationReader(
Dex dex, AnnotatedElement element, String annotationName, int expectedFieldCount) {
int annotationSetOffset = getAnnotationSetOffset(element);
diff --git a/luni/src/main/java/libcore/reflect/GenericArrayTypeImpl.java b/luni/src/main/java/libcore/reflect/GenericArrayTypeImpl.java
index ef22576..5919a19 100644
--- a/luni/src/main/java/libcore/reflect/GenericArrayTypeImpl.java
+++ b/luni/src/main/java/libcore/reflect/GenericArrayTypeImpl.java
@@ -18,6 +18,7 @@ package libcore.reflect;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
+import java.util.Objects;
public final class GenericArrayTypeImpl implements GenericArrayType {
private final Type componentType;
@@ -34,6 +35,20 @@ public final class GenericArrayTypeImpl implements GenericArrayType {
}
}
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof GenericArrayType)) {
+ return false;
+ }
+ GenericArrayType that = (GenericArrayType) o;
+ return Objects.equals(getGenericComponentType(), that.getGenericComponentType());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getGenericComponentType());
+ }
+
public String toString() {
return componentType.toString() + "[]";
}
diff --git a/luni/src/main/java/libcore/reflect/ParameterizedTypeImpl.java b/luni/src/main/java/libcore/reflect/ParameterizedTypeImpl.java
index 99dfe8b..2cd5ac3 100644
--- a/luni/src/main/java/libcore/reflect/ParameterizedTypeImpl.java
+++ b/luni/src/main/java/libcore/reflect/ParameterizedTypeImpl.java
@@ -18,17 +18,22 @@ package libcore.reflect;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Objects;
public final class ParameterizedTypeImpl implements ParameterizedType {
private final ListOfTypes args;
private final ParameterizedTypeImpl ownerType0; // Potentially unresolved.
- private Type ownerTypeRes;
- private Class rawType; // Already resolved.
+ private Type ownerTypeRes; // Potentially unresolved.
+ private Class rawType; // Potentially unresolved.
private final String rawTypeName;
- private ClassLoader loader;
+ private final ClassLoader loader;
public ParameterizedTypeImpl(ParameterizedTypeImpl ownerType, String rawTypeName,
ListOfTypes args, ClassLoader loader) {
+ if (args == null) {
+ throw new NullPointerException();
+ }
this.ownerType0 = ownerType;
this.rawTypeName = rawTypeName;
this.args = args;
@@ -37,7 +42,6 @@ public final class ParameterizedTypeImpl implements ParameterizedType {
public Type[] getActualTypeArguments() {
- // ASSUMPTION: args is never null!!!
return args.getResolvedTypes().clone();
}
@@ -76,6 +80,23 @@ public final class ParameterizedTypeImpl implements ParameterizedType {
}
@Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ParameterizedType)) {
+ return false;
+ }
+ ParameterizedType that = (ParameterizedType) o;
+ return Objects.equals(getRawType(), that.getRawType()) &&
+ Objects.equals(getOwnerType(), that.getOwnerType()) &&
+ Arrays.equals(args.getResolvedTypes(), that.getActualTypeArguments());
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * (31 * Objects.hashCode(getRawType()) + Objects.hashCode(getOwnerType())) +
+ Arrays.hashCode(args.getResolvedTypes());
+ }
+
+ @Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(rawTypeName);
diff --git a/luni/src/main/java/libcore/util/EmptyArray.java b/luni/src/main/java/libcore/util/EmptyArray.java
index 1713bfc..eac06f2 100644
--- a/luni/src/main/java/libcore/util/EmptyArray.java
+++ b/luni/src/main/java/libcore/util/EmptyArray.java
@@ -23,7 +23,9 @@ public final class EmptyArray {
public static final byte[] BYTE = new byte[0];
public static final char[] CHAR = new char[0];
public static final double[] DOUBLE = new double[0];
+ public static final float[] FLOAT = new float[0];
public static final int[] INT = new int[0];
+ public static final long[] LONG = new long[0];
public static final Class<?>[] CLASS = new Class[0];
public static final Object[] OBJECT = new Object[0];
diff --git a/luni/src/main/java/libcore/util/ZoneInfo.java b/luni/src/main/java/libcore/util/ZoneInfo.java
index 54ee667..4d58d93 100644
--- a/luni/src/main/java/libcore/util/ZoneInfo.java
+++ b/luni/src/main/java/libcore/util/ZoneInfo.java
@@ -13,11 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+/*
+ * Elements of the WallTime class are a port of Bionic's localtime.c to Java. That code had the
+ * following header:
+ *
+ * This file is in the public domain, so clarified as of
+ * 1996-06-05 by Arthur David Olson.
+ */
package libcore.util;
import java.util.Arrays;
+import java.util.Calendar;
import java.util.Date;
+import java.util.GregorianCalendar;
import java.util.TimeZone;
import libcore.io.BufferIterator;
@@ -51,7 +59,7 @@ public final class ZoneInfo extends TimeZone {
private final byte[] mTypes;
private final byte[] mIsDsts;
- public static TimeZone makeTimeZone(String id, BufferIterator it) {
+ public static ZoneInfo makeTimeZone(String id, BufferIterator it) {
// Variable names beginning tzh_ correspond to those in "tzfile.h".
// Check tzh_magic.
@@ -148,22 +156,21 @@ public final class ZoneInfo extends TimeZone {
mOffsets[i] -= mRawOffset;
}
- // Is this zone still observing DST?
+ // Is this zone observing DST currently or in the future?
// We don't care if they've historically used it: most places have at least once.
- // We want to know whether the last "schedule info" (the unix times in the mTransitions
- // array) is in the future. If it is, DST is still relevant.
// See http://code.google.com/p/android/issues/detail?id=877.
// This test means that for somewhere like Morocco, which tried DST in 2009 but has
// no future plans (and thus no future schedule info) will report "true" from
// useDaylightTime at the start of 2009 but "false" at the end. This seems appropriate.
boolean usesDst = false;
- long currentUnixTime = System.currentTimeMillis() / 1000;
- if (mTransitions.length > 0) {
- // (We're really dealing with uint32_t values, so long is most convenient in Java.)
- long latestScheduleTime = ((long) mTransitions[mTransitions.length - 1]) & 0xffffffff;
- if (currentUnixTime < latestScheduleTime) {
+ int currentUnixTimeSeconds = (int) (System.currentTimeMillis() / 1000);
+ int i = mTransitions.length - 1;
+ while (i >= 0 && mTransitions[i] >= currentUnixTimeSeconds) {
+ if (mIsDsts[mTypes[i]] > 0) {
usesDst = true;
+ break;
}
+ i--;
}
mUseDst = usesDst;
@@ -301,4 +308,670 @@ public final class ZoneInfo extends TimeZone {
",transitions=" + mTransitions.length +
"]";
}
+
+ @Override
+ public Object clone() {
+ // Overridden for documentation. The default clone() behavior is exactly what we want.
+ // Though mutable, the arrays of offset data are treated as immutable. Only ID and
+ // mRawOffset are mutable in this class, and those are an immutable object and a primitive
+ // respectively.
+ return super.clone();
+ }
+
+ /**
+ * A class that represents a "wall time". This class is modeled on the C tm struct and
+ * is used to support android.text.format.Time behavior. Unlike the tm struct the year is
+ * represented as the full year, not the years since 1900.
+ *
+ * <p>This class contains a rewrite of various native functions that android.text.format.Time
+ * once relied on such as mktime_tz and localtime_tz. This replacement does not support leap
+ * seconds but does try to preserve behavior around ambiguous date/times found in the BSD
+ * version of mktime that was previously used.
+ *
+ * <p>The original native code used a 32-bit value for time_t on 32-bit Android, which
+ * was the only variant of Android available at the time. To preserve old behavior this code
+ * deliberately uses {@code int} rather than {@code long} for most things and performs
+ * calculations in seconds. This creates deliberate truncation issues for date / times before
+ * 1901 and after 2038. This is intentional but might be fixed in future if all the knock-ons
+ * can be resolved: Application code may have come to rely on the range so previously values
+ * like zero for year could indicate an invalid date but if we move to long the year zero would
+ * be valid.
+ *
+ * <p>All offsets are considered to be safe for addition / subtraction / multiplication without
+ * worrying about overflow. All absolute time arithmetic is checked for overflow / underflow.
+ */
+ public static class WallTime {
+
+ // We use a GregorianCalendar (set to UTC) to handle all the date/time normalization logic
+ // and to convert from a broken-down date/time to a millis value.
+ // Unfortunately, it cannot represent an initial state with a zero day and would
+ // automatically normalize it, so we must copy values into and out of it as needed.
+ private final GregorianCalendar calendar;
+
+ private int year;
+ private int month;
+ private int monthDay;
+ private int hour;
+ private int minute;
+ private int second;
+ private int weekDay;
+ private int yearDay;
+ private int isDst;
+ private int gmtOffsetSeconds;
+
+ public WallTime() {
+ this.calendar = createGregorianCalendar();
+ calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ // LayoutLib replaces this method via bytecode manipulation, since the
+ // minimum-cost constructor is not available on host machines.
+ private static GregorianCalendar createGregorianCalendar() {
+ return new GregorianCalendar(false);
+ }
+
+ /**
+ * Sets the wall time to a point in time using the time zone information provided. This
+ * is a replacement for the old native localtime_tz() function.
+ *
+ * <p>When going from an instant to a wall time it is always unambiguous because there
+ * is only one offset rule acting at any given instant. We do not consider leap seconds.
+ */
+ public void localtime(int timeSeconds, ZoneInfo zoneInfo) {
+ try {
+ int offsetSeconds = zoneInfo.mRawOffset / 1000;
+
+ // Find out the timezone DST state and adjustment.
+ byte isDst;
+ if (zoneInfo.mTransitions.length == 0) {
+ isDst = 0;
+ } else {
+ // transitionIndex can be in the range -1..zoneInfo.mTransitions.length - 1
+ int transitionIndex = findTransitionIndex(zoneInfo, timeSeconds);
+ if (transitionIndex < 0) {
+ // -1 means timeSeconds is "before the first recorded transition". The first
+ // recorded transition is treated as a transition from non-DST and the raw
+ // offset.
+ isDst = 0;
+ } else {
+ byte transitionType = zoneInfo.mTypes[transitionIndex];
+ offsetSeconds += zoneInfo.mOffsets[transitionType];
+ isDst = zoneInfo.mIsDsts[transitionType];
+ }
+ }
+
+ // Perform arithmetic that might underflow before setting fields.
+ int wallTimeSeconds = checkedAdd(timeSeconds, offsetSeconds);
+
+ // Set fields.
+ calendar.setTimeInMillis(wallTimeSeconds * 1000L);
+ copyFieldsFromCalendar();
+ this.isDst = isDst;
+ this.gmtOffsetSeconds = offsetSeconds;
+ } catch (CheckedArithmeticException e) {
+ // Just stop, leaving fields untouched.
+ }
+ }
+
+ /**
+ * Returns the time in seconds since beginning of the Unix epoch for the wall time using the
+ * time zone information provided. This is a replacement for an old native mktime_tz() C
+ * function.
+ *
+ * <p>When going from a wall time to an instant the answer can be ambiguous. A wall
+ * time can map to zero, one or two instants given sane date/time transitions. Sane
+ * in this case means that transitions occur less frequently than the offset
+ * differences between them (which could cause all sorts of craziness like the
+ * skipping out of transitions).
+ *
+ * <p>For example, this is not fully supported:
+ * <ul>
+ * <li>t1 { time = 1, offset = 0 }
+ * <li>t2 { time = 2, offset = -1 }
+ * <li>t3 { time = 3, offset = -2 }
+ * </ul>
+ * A wall time in this case might map to t1, t2 or t3.
+ *
+ * <p>We do not handle leap seconds.
+ * <p>We assume that no timezone offset transition has an absolute offset > 24 hours.
+ * <p>We do not assume that adjacent transitions modify the DST state; adjustments can
+ * occur for other reasons such as when a zone changes its raw offset.
+ */
+ public int mktime(ZoneInfo zoneInfo) {
+ // Normalize isDst to -1, 0 or 1 to simplify isDst equality checks below.
+ this.isDst = this.isDst > 0 ? this.isDst = 1 : this.isDst < 0 ? this.isDst = -1 : 0;
+
+ copyFieldsToCalendar();
+ final long longWallTimeSeconds = calendar.getTimeInMillis() / 1000;
+ if (Integer.MIN_VALUE > longWallTimeSeconds
+ || longWallTimeSeconds > Integer.MAX_VALUE) {
+ // For compatibility with the old native 32-bit implementation we must treat
+ // this as an error. Note: -1 could be confused with a real time.
+ return -1;
+ }
+
+ try {
+ final int wallTimeSeconds = (int) longWallTimeSeconds;
+ final int rawOffsetSeconds = zoneInfo.mRawOffset / 1000;
+ final int rawTimeSeconds = checkedSubtract(wallTimeSeconds, rawOffsetSeconds);
+
+ if (zoneInfo.mTransitions.length == 0) {
+ // There is no transition information. There is just a raw offset for all time.
+ if (this.isDst > 0) {
+ // Caller has asserted DST, but there is no DST information available.
+ return -1;
+ }
+ copyFieldsFromCalendar();
+ this.isDst = 0;
+ this.gmtOffsetSeconds = rawOffsetSeconds;
+ return rawTimeSeconds;
+ }
+
+ // We cannot know for sure what instant the wall time will map to. Unfortunately, in
+ // order to know for sure we need the timezone information, but to get the timezone
+ // information we need an instant. To resolve this we use the raw offset to find an
+ // OffsetInterval; this will get us the OffsetInterval we need or very close.
+
+ // The initialTransition can be between -1 and (zoneInfo.mTransitions - 1). -1
+ // indicates the rawTime is before the first transition and is handled gracefully by
+ // createOffsetInterval().
+ final int initialTransitionIndex = findTransitionIndex(zoneInfo, rawTimeSeconds);
+
+ if (isDst < 0) {
+ // This is treated as a special case to get it out of the way:
+ // When a caller has set isDst == -1 it means we can return the first match for
+ // the wall time we find. If the caller has specified a wall time that cannot
+ // exist this always returns -1.
+
+ Integer result = doWallTimeSearch(zoneInfo, initialTransitionIndex,
+ wallTimeSeconds, true /* mustMatchDst */);
+ return result == null ? -1 : result;
+ }
+
+ // If the wall time asserts a DST (isDst == 0 or 1) the search is performed twice:
+ // 1) The first attempts to find a DST offset that matches isDst exactly.
+ // 2) If it fails, isDst is assumed to be incorrect and adjustments are made to see
+ // if a valid wall time can be created. The result can be somewhat arbitrary.
+
+ Integer result = doWallTimeSearch(zoneInfo, initialTransitionIndex, wallTimeSeconds,
+ true /* mustMatchDst */);
+ if (result == null) {
+ result = doWallTimeSearch(zoneInfo, initialTransitionIndex, wallTimeSeconds,
+ false /* mustMatchDst */);
+ }
+ if (result == null) {
+ result = -1;
+ }
+ return result;
+ } catch (CheckedArithmeticException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Attempt to apply DST adjustments to {@code oldWallTimeSeconds} to create a wall time in
+ * {@code targetInterval}.
+ *
+ * <p>This is used when a caller has made an assertion about standard time / DST that cannot
+ * be matched to any offset interval that exists. We must therefore assume that the isDst
+ * assertion is incorrect and the invalid wall time is the result of some modification the
+ * caller made to a valid wall time that pushed them outside of the offset interval they
+ * were in. We must correct for any DST change that should have been applied when they did
+ * so.
+ *
+ * <p>Unfortunately, we have no information about what adjustment they made and so cannot
+ * know which offset interval they were previously in. For example, they may have added a
+ * second or a year to a valid time to arrive at what they have.
+ *
+ * <p>We try all offset types that are not the same as the isDst the caller asserted. For
+ * each possible offset we work out the offset difference between that and
+ * {@code targetInterval}, apply it, and see if we are still in {@code targetInterval}. If
+ * we are, then we have found an adjustment.
+ */
+ private Integer tryOffsetAdjustments(ZoneInfo zoneInfo, int oldWallTimeSeconds,
+ OffsetInterval targetInterval, int transitionIndex, int isDstToFind)
+ throws CheckedArithmeticException {
+
+ int[] offsetsToTry = getOffsetsOfType(zoneInfo, transitionIndex, isDstToFind);
+ for (int j = 0; j < offsetsToTry.length; j++) {
+ int rawOffsetSeconds = zoneInfo.mRawOffset / 1000;
+ int jOffsetSeconds = rawOffsetSeconds + offsetsToTry[j];
+ int targetIntervalOffsetSeconds = targetInterval.getTotalOffsetSeconds();
+ int adjustmentSeconds = targetIntervalOffsetSeconds - jOffsetSeconds;
+ int adjustedWallTimeSeconds = checkedAdd(oldWallTimeSeconds, adjustmentSeconds);
+ if (targetInterval.containsWallTime(adjustedWallTimeSeconds)) {
+ // Perform any arithmetic that might overflow.
+ int returnValue = checkedSubtract(adjustedWallTimeSeconds,
+ targetIntervalOffsetSeconds);
+
+ // Modify field state and return the result.
+ calendar.setTimeInMillis(adjustedWallTimeSeconds * 1000L);
+ copyFieldsFromCalendar();
+ this.isDst = targetInterval.getIsDst();
+ this.gmtOffsetSeconds = targetIntervalOffsetSeconds;
+ return returnValue;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return an array of offsets that have the requested {@code isDst} value.
+ * The {@code startIndex} is used as a starting point so transitions nearest
+ * to that index are returned first.
+ */
+ private static int[] getOffsetsOfType(ZoneInfo zoneInfo, int startIndex, int isDst) {
+ // +1 to account for the synthetic transition we invent before the first recorded one.
+ int[] offsets = new int[zoneInfo.mOffsets.length + 1];
+ boolean[] seen = new boolean[zoneInfo.mOffsets.length];
+ int numFound = 0;
+
+ int delta = 0;
+ boolean clampTop = false;
+ boolean clampBottom = false;
+ do {
+ // delta = { 1, -1, 2, -2, 3, -3...}
+ delta *= -1;
+ if (delta >= 0) {
+ delta++;
+ }
+
+ int transitionIndex = startIndex + delta;
+ if (delta < 0 && transitionIndex < -1) {
+ clampBottom = true;
+ continue;
+ } else if (delta > 0 && transitionIndex >= zoneInfo.mTypes.length) {
+ clampTop = true;
+ continue;
+ }
+
+ if (transitionIndex == -1) {
+ if (isDst == 0) {
+ // Synthesize a non-DST transition before the first transition we have
+ // data for.
+ offsets[numFound++] = 0; // offset of 0 from raw offset
+ }
+ continue;
+ }
+ byte type = zoneInfo.mTypes[transitionIndex];
+ if (!seen[type]) {
+ if (zoneInfo.mIsDsts[type] == isDst) {
+ offsets[numFound++] = zoneInfo.mOffsets[type];
+ }
+ seen[type] = true;
+ }
+ } while (!(clampTop && clampBottom));
+
+ int[] toReturn = new int[numFound];
+ System.arraycopy(offsets, 0, toReturn, 0, numFound);
+ return toReturn;
+ }
+
+ /**
+ * Find a time <em>in seconds</em> the same or close to {@code wallTimeSeconds} that
+ * satisfies {@code mustMatchDst}. The search begins around the timezone offset transition
+ * with {@code initialTransitionIndex}.
+ *
+ * <p>If {@code mustMatchDst} is {@code true} the method can only return times that
+ * use timezone offsets that satisfy the {@code this.isDst} requirements.
+ * If {@code this.isDst == -1} it means that any offset can be used.
+ *
+ * <p>If {@code mustMatchDst} is {@code false} any offset that covers the
+ * currently set time is acceptable. That is: if {@code this.isDst} == -1, any offset
+ * transition can be used, if it is 0 or 1 the offset used must match {@code this.isDst}.
+ *
+ * <p>Note: This method both uses and can modify field state. It returns the matching time
+ * in seconds if a match has been found and modifies fields, or it returns {@code null} and
+ * leaves the field state unmodified.
+ */
+ private Integer doWallTimeSearch(ZoneInfo zoneInfo, int initialTransitionIndex,
+ int wallTimeSeconds, boolean mustMatchDst) throws CheckedArithmeticException {
+
+ // The loop below starts at the initialTransitionIndex and radiates out from that point
+ // up to 24 hours in either direction by applying transitionIndexDelta to inspect
+ // adjacent transitions (0, -1, +1, -2, +2). 24 hours is used because we assume that no
+ // total offset from UTC is ever > 24 hours. clampTop and clampBottom are used to
+ // indicate whether the search has either searched > 24 hours or exhausted the
+ // transition data in that direction. The search stops when a match is found or if
+ // clampTop and clampBottom are both true.
+ // The match logic employed is determined by the mustMatchDst parameter.
+ final int MAX_SEARCH_SECONDS = 24 * 60 * 60;
+ boolean clampTop = false, clampBottom = false;
+ int loop = 0;
+ do {
+ // transitionIndexDelta = { 0, -1, 1, -2, 2,..}
+ int transitionIndexDelta = (loop + 1) / 2;
+ if (loop % 2 == 1) {
+ transitionIndexDelta *= -1;
+ }
+ loop++;
+
+ // Only do any work in this iteration if we need to.
+ if (transitionIndexDelta > 0 && clampTop
+ || transitionIndexDelta < 0 && clampBottom) {
+ continue;
+ }
+
+ // Obtain the OffsetInterval to use.
+ int currentTransitionIndex = initialTransitionIndex + transitionIndexDelta;
+ OffsetInterval offsetInterval =
+ OffsetInterval.create(zoneInfo, currentTransitionIndex);
+ if (offsetInterval == null) {
+ // No transition exists with the index we tried: Stop searching in the
+ // current direction.
+ clampTop |= (transitionIndexDelta > 0);
+ clampBottom |= (transitionIndexDelta < 0);
+ continue;
+ }
+
+ // Match the wallTimeSeconds against the OffsetInterval.
+ if (mustMatchDst) {
+ // Work out if the interval contains the wall time the caller specified and
+ // matches their isDst value.
+ if (offsetInterval.containsWallTime(wallTimeSeconds)) {
+ if (this.isDst == -1 || offsetInterval.getIsDst() == this.isDst) {
+ // This always returns the first OffsetInterval it finds that matches
+ // the wall time and isDst requirements. If this.isDst == -1 this means
+ // the result might be a DST or a non-DST answer for wall times that can
+ // exist in two OffsetIntervals.
+ int totalOffsetSeconds = offsetInterval.getTotalOffsetSeconds();
+ int returnValue = checkedSubtract(wallTimeSeconds,
+ totalOffsetSeconds);
+
+ copyFieldsFromCalendar();
+ this.isDst = offsetInterval.getIsDst();
+ this.gmtOffsetSeconds = totalOffsetSeconds;
+ return returnValue;
+ }
+ }
+ } else {
+ // To retain similar behavior to the old native implementation: if the caller is
+ // asserting the same isDst value as the OffsetInterval we are looking at we do
+ // not try to find an adjustment from another OffsetInterval of the same isDst
+ // type. If you remove this you get different results in situations like a
+ // DST -> DST transition or STD -> STD transition that results in an interval of
+ // "skipped" wall time. For example: if 01:30 (DST) is invalid and between two
+ // DST intervals, and the caller has passed isDst == 1, this results in a -1
+ // being returned.
+ if (isDst != offsetInterval.getIsDst()) {
+ final int isDstToFind = isDst;
+ Integer returnValue = tryOffsetAdjustments(zoneInfo, wallTimeSeconds,
+ offsetInterval, currentTransitionIndex, isDstToFind);
+ if (returnValue != null) {
+ return returnValue;
+ }
+ }
+ }
+
+ // See if we can avoid another loop in the current direction.
+ if (transitionIndexDelta > 0) {
+ // If we are searching forward and the OffsetInterval we have ends
+ // > MAX_SEARCH_SECONDS after the wall time, we don't need to look any further
+ // forward.
+ boolean endSearch = offsetInterval.getEndWallTimeSeconds() - wallTimeSeconds
+ > MAX_SEARCH_SECONDS;
+ if (endSearch) {
+ clampTop = true;
+ }
+ } else if (transitionIndexDelta < 0) {
+ boolean endSearch = wallTimeSeconds - offsetInterval.getStartWallTimeSeconds()
+ >= MAX_SEARCH_SECONDS;
+ if (endSearch) {
+ // If we are searching backward and the OffsetInterval starts
+ // > MAX_SEARCH_SECONDS before the wall time, we don't need to look any
+ // further backwards.
+ clampBottom = true;
+ }
+ }
+ } while (!(clampTop && clampBottom));
+ return null;
+ }
+
+ public void setYear(int year) {
+ this.year = year;
+ }
+
+ public void setMonth(int month) {
+ this.month = month;
+ }
+
+ public void setMonthDay(int monthDay) {
+ this.monthDay = monthDay;
+ }
+
+ public void setHour(int hour) {
+ this.hour = hour;
+ }
+
+ public void setMinute(int minute) {
+ this.minute = minute;
+ }
+
+ public void setSecond(int second) {
+ this.second = second;
+ }
+
+ public void setWeekDay(int weekDay) {
+ this.weekDay = weekDay;
+ }
+
+ public void setYearDay(int yearDay) {
+ this.yearDay = yearDay;
+ }
+
+ public void setIsDst(int isDst) {
+ this.isDst = isDst;
+ }
+
+ public void setGmtOffset(int gmtoff) {
+ this.gmtOffsetSeconds = gmtoff;
+ }
+
+ public int getYear() {
+ return year;
+ }
+
+ public int getMonth() {
+ return month;
+ }
+
+ public int getMonthDay() {
+ return monthDay;
+ }
+
+ public int getHour() {
+ return hour;
+ }
+
+ public int getMinute() {
+ return minute;
+ }
+
+ public int getSecond() {
+ return second;
+ }
+
+ public int getWeekDay() {
+ return weekDay;
+ }
+
+ public int getYearDay() {
+ return yearDay;
+ }
+
+ public int getGmtOffset() {
+ return gmtOffsetSeconds;
+ }
+
+ public int getIsDst() {
+ return isDst;
+ }
+
+ private void copyFieldsToCalendar() {
+ calendar.set(Calendar.YEAR, year);
+ calendar.set(Calendar.MONTH, month);
+ calendar.set(Calendar.DAY_OF_MONTH, monthDay);
+ calendar.set(Calendar.HOUR_OF_DAY, hour);
+ calendar.set(Calendar.MINUTE, minute);
+ calendar.set(Calendar.SECOND, second);
+ }
+
+ private void copyFieldsFromCalendar() {
+ year = calendar.get(Calendar.YEAR);
+ month = calendar.get(Calendar.MONTH);
+ monthDay = calendar.get(Calendar.DAY_OF_MONTH);
+ hour = calendar.get(Calendar.HOUR_OF_DAY);
+ minute = calendar.get(Calendar.MINUTE);
+ second = calendar.get(Calendar.SECOND);
+
+ // Calendar uses Sunday == 1. Android Time uses Sunday = 0.
+ weekDay = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+ // Calendar enumerates from 1, Android Time enumerates from 0.
+ yearDay = calendar.get(Calendar.DAY_OF_YEAR) - 1;
+ }
+
+ /**
+ * Find the transition in the {@code timezone} in effect at {@code timeSeconds}.
+ *
+ * <p>Returns an index in the range -1..timeZone.mTransitions.length - 1. -1 is used to
+ * indicate the time is before the first transition. Other values are an index into
+ * timeZone.mTransitions.
+ */
+ private static int findTransitionIndex(ZoneInfo timeZone, int timeSeconds) {
+ int matchingRawTransition = Arrays.binarySearch(timeZone.mTransitions, timeSeconds);
+ if (matchingRawTransition < 0) {
+ matchingRawTransition = ~matchingRawTransition - 1;
+ }
+ return matchingRawTransition;
+ }
+ }
+
+ /**
+ * A wall-time representation of a timezone offset interval.
+ *
+ * <p>Wall-time means "as it would appear locally in the timezone in which it applies".
+ * For example in 2007:
+ * PST was a -8:00 offset that ran until Mar 11, 2:00 AM.
+ * PDT was a -7:00 offset and ran from Mar 11, 3:00 AM to Nov 4, 2:00 AM.
+ * PST was a -8:00 offset and ran from Nov 4, 1:00 AM.
+ * Crucially this means that there was a "gap" after PST when PDT started, and an overlap when
+ * PDT ended and PST began.
+ *
+ * <p>For convenience all wall-time values are represented as the number of seconds since the
+ * beginning of the Unix epoch <em>in UTC</em>. To convert from a wall-time to the actual time
+ * in the offset it is necessary to <em>subtract</em> the {@code totalOffsetSeconds}.
+ * For example: If the offset in PST is -07:00 hours, then:
+ * timeInPstSeconds = wallTimeUtcSeconds - offsetSeconds
+ * i.e. 13:00 UTC - (-07:00) = 20:00 UTC = 13:00 PST
+ */
+ static class OffsetInterval {
+
+ private final int startWallTimeSeconds;
+ private final int endWallTimeSeconds;
+ private final int isDst;
+ private final int totalOffsetSeconds;
+
+ /**
+ * Creates an {@link OffsetInterval}.
+ *
+ * <p>If {@code transitionIndex} is -1, the transition is synthesized to be a non-DST offset
+ * that runs from the beginning of time until the first transition in {@code timeZone} and
+ * has an offset of {@code timezone.mRawOffset}. If {@code transitionIndex} is the last
+ * transition that transition is considered to run until the end of representable time.
+ * Otherwise, the information is extracted from {@code timeZone.mTransitions},
+ * {@code timeZone.mOffsets} an {@code timeZone.mIsDsts}.
+ */
+ public static OffsetInterval create(ZoneInfo timeZone, int transitionIndex)
+ throws CheckedArithmeticException {
+
+ if (transitionIndex < -1 || transitionIndex >= timeZone.mTransitions.length) {
+ return null;
+ }
+
+ int rawOffsetSeconds = timeZone.mRawOffset / 1000;
+ if (transitionIndex == -1) {
+ int endWallTimeSeconds = checkedAdd(timeZone.mTransitions[0], rawOffsetSeconds);
+ return new OffsetInterval(Integer.MIN_VALUE, endWallTimeSeconds, 0 /* isDst */,
+ rawOffsetSeconds);
+ }
+
+ byte type = timeZone.mTypes[transitionIndex];
+ int totalOffsetSeconds = timeZone.mOffsets[type] + rawOffsetSeconds;
+ int endWallTimeSeconds;
+ if (transitionIndex == timeZone.mTransitions.length - 1) {
+ // If this is the last transition, make up the end time.
+ endWallTimeSeconds = Integer.MAX_VALUE;
+ } else {
+ endWallTimeSeconds = checkedAdd(timeZone.mTransitions[transitionIndex + 1],
+ totalOffsetSeconds);
+ }
+ int isDst = timeZone.mIsDsts[type];
+ int startWallTimeSeconds =
+ checkedAdd(timeZone.mTransitions[transitionIndex], totalOffsetSeconds);
+ return new OffsetInterval(
+ startWallTimeSeconds, endWallTimeSeconds, isDst, totalOffsetSeconds);
+ }
+
+ private OffsetInterval(int startWallTimeSeconds, int endWallTimeSeconds, int isDst,
+ int totalOffsetSeconds) {
+ this.startWallTimeSeconds = startWallTimeSeconds;
+ this.endWallTimeSeconds = endWallTimeSeconds;
+ this.isDst = isDst;
+ this.totalOffsetSeconds = totalOffsetSeconds;
+ }
+
+ public boolean containsWallTime(long wallTimeSeconds) {
+ return wallTimeSeconds >= startWallTimeSeconds && wallTimeSeconds < endWallTimeSeconds;
+ }
+
+ public int getIsDst() {
+ return isDst;
+ }
+
+ public int getTotalOffsetSeconds() {
+ return totalOffsetSeconds;
+ }
+
+ public long getEndWallTimeSeconds() {
+ return endWallTimeSeconds;
+ }
+
+ public long getStartWallTimeSeconds() {
+ return startWallTimeSeconds;
+ }
+ }
+
+ /**
+ * An exception used to indicate an arithmetic overflow or underflow.
+ */
+ private static class CheckedArithmeticException extends Exception {
+ }
+
+ /**
+ * Calculate (a + b).
+ *
+ * @throws CheckedArithmeticException if overflow or underflow occurs
+ */
+ private static int checkedAdd(int a, int b) throws CheckedArithmeticException {
+ // Adapted from Guava IntMath.checkedAdd();
+ long result = (long) a + b;
+ if (result != (int) result) {
+ throw new CheckedArithmeticException();
+ }
+ return (int) result;
+ }
+
+ /**
+ * Calculate (a - b).
+ *
+ * @throws CheckedArithmeticException if overflow or underflow occurs
+ */
+ private static int checkedSubtract(int a, int b) throws CheckedArithmeticException {
+ // Adapted from Guava IntMath.checkedSubtract();
+ long result = (long) a - b;
+ if (result != (int) result) {
+ throw new CheckedArithmeticException();
+ }
+ return (int) result;
+ }
}
diff --git a/luni/src/main/java/libcore/util/ZoneInfoDB.java b/luni/src/main/java/libcore/util/ZoneInfoDB.java
index 10e3900..a9d06a4 100644
--- a/luni/src/main/java/libcore/util/ZoneInfoDB.java
+++ b/luni/src/main/java/libcore/util/ZoneInfoDB.java
@@ -16,6 +16,7 @@
package libcore.util;
+import android.system.ErrnoException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
@@ -27,7 +28,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.TimeZone;
import libcore.io.BufferIterator;
-import libcore.io.ErrnoException;
import libcore.io.IoUtils;
import libcore.io.MemoryMappedFile;
@@ -62,11 +62,33 @@ public final class ZoneInfoDB {
/**
* The 'ids' array contains time zone ids sorted alphabetically, for binary searching.
* The other two arrays are in the same order. 'byteOffsets' gives the byte offset
- * of each time zone, and 'rawUtcOffsets' gives the time zone's raw UTC offset.
+ * of each time zone, and 'rawUtcOffsetsCache' gives the time zone's raw UTC offset.
*/
private String[] ids;
private int[] byteOffsets;
- private int[] rawUtcOffsets;
+ private int[] rawUtcOffsetsCache; // Access this via getRawUtcOffsets instead.
+
+ /**
+ * ZoneInfo objects are worth caching because they are expensive to create.
+ * See http://b/8270865 for context.
+ */
+ private final static int CACHE_SIZE = 1;
+ private final BasicLruCache<String, ZoneInfo> cache =
+ new BasicLruCache<String, ZoneInfo>(CACHE_SIZE) {
+ @Override
+ protected ZoneInfo create(String id) {
+ // Work out where in the big data file this time zone is.
+ int index = Arrays.binarySearch(ids, id);
+ if (index < 0) {
+ return null;
+ }
+
+ BufferIterator it = mappedFile.bigEndianIterator();
+ it.skip(byteOffsets[index]);
+
+ return ZoneInfo.makeTimeZone(id, it);
+ }
+ };
public TzData(String... paths) {
for (String path : paths) {
@@ -82,7 +104,7 @@ public final class ZoneInfoDB {
version = "missing";
zoneTab = "# Emergency fallback data.\n";
ids = new String[] { "GMT" };
- byteOffsets = rawUtcOffsets = new int[1];
+ byteOffsets = rawUtcOffsetsCache = new int[1];
}
private boolean loadData(String path) {
@@ -149,7 +171,6 @@ public final class ZoneInfoDB {
int idOffset = 0;
byteOffsets = new int[entryCount];
- rawUtcOffsets = new int[entryCount];
for (int i = 0; i < entryCount; i++) {
it.readByteArray(idBytes, 0, idBytes.length);
@@ -161,7 +182,7 @@ public final class ZoneInfoDB {
if (length < 44) {
throw new AssertionError("length in index file < sizeof(tzhead)");
}
- rawUtcOffsets[i] = it.readInt();
+ it.skip(4); // Skip the unused 4 bytes that used to be the raw offset.
// Don't include null chars in the String
int len = idBytes.length;
@@ -188,16 +209,33 @@ public final class ZoneInfoDB {
return ids.clone();
}
- public String[] getAvailableIDs(int rawOffset) {
+ public String[] getAvailableIDs(int rawUtcOffset) {
List<String> matches = new ArrayList<String>();
- for (int i = 0, end = rawUtcOffsets.length; i < end; ++i) {
- if (rawUtcOffsets[i] == rawOffset) {
+ int[] rawUtcOffsets = getRawUtcOffsets();
+ for (int i = 0; i < rawUtcOffsets.length; ++i) {
+ if (rawUtcOffsets[i] == rawUtcOffset) {
matches.add(ids[i]);
}
}
return matches.toArray(new String[matches.size()]);
}
+ private synchronized int[] getRawUtcOffsets() {
+ if (rawUtcOffsetsCache != null) {
+ return rawUtcOffsetsCache;
+ }
+ rawUtcOffsetsCache = new int[ids.length];
+ for (int i = 0; i < ids.length; ++i) {
+ // This creates a TimeZone, which is quite expensive. Hence the cache.
+ // Note that icu4c does the same (without the cache), so if you're
+ // switching this code over to icu4j you should check its performance.
+ // Telephony shouldn't care, but someone converting a bunch of calendar
+ // events might.
+ rawUtcOffsetsCache[i] = cache.get(ids[i]).getRawOffset();
+ }
+ return rawUtcOffsetsCache;
+ }
+
public String getVersion() {
return version;
}
@@ -206,17 +244,10 @@ public final class ZoneInfoDB {
return zoneTab;
}
- public TimeZone makeTimeZone(String id) throws IOException {
- // Work out where in the big data file this time zone is.
- int index = Arrays.binarySearch(ids, id);
- if (index < 0) {
- return null;
- }
-
- BufferIterator it = mappedFile.bigEndianIterator();
- it.skip(byteOffsets[index]);
-
- return ZoneInfo.makeTimeZone(id, it);
+ public ZoneInfo makeTimeZone(String id) throws IOException {
+ ZoneInfo zoneInfo = cache.get(id);
+ // The object from the cache is cloned because TimeZone / ZoneInfo are mutable.
+ return zoneInfo == null ? null : (ZoneInfo) zoneInfo.clone();
}
}
diff --git a/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java b/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java
index f1dd43c..855a8c7 100644
--- a/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java
+++ b/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java
@@ -24,15 +24,15 @@ package org.apache.harmony.security.fortress;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
+import java.util.ArrayList;
import java.util.Locale;
-
/**
* This class implements common functionality for Provider supplied
* classes. The usage pattern is to allocate static Engine instance
* per service type and synchronize on that instance during calls to
- * {@code getInstance} and retreival of the selected {@code Provider}
- * and Service Provider Interface (SPI) results. Retreiving the
+ * {@code getInstance} and retrieval of the selected {@code Provider}
+ * and Service Provider Interface (SPI) results. Retrieving the
* results with {@code getProvider} and {@code getSpi} sets the
* internal {@code Engine} values to null to prevent memory leaks.
*
@@ -69,7 +69,7 @@ import java.util.Locale;
*
* }</pre>
*/
-public class Engine {
+public final class Engine {
/**
* Access to package visible api in java.security
@@ -95,14 +95,14 @@ public class Engine {
/** used to test for cache validity */
private final int cacheVersion;
/** cached result */
- private final Provider.Service service;
+ private final ArrayList<Provider.Service> services;
private ServiceCacheEntry(String algorithm,
int cacheVersion,
- Provider.Service service) {
+ ArrayList<Provider.Service> services) {
this.algorithm = algorithm;
this.cacheVersion = cacheVersion;
- this.service = service;
+ this.services = services;
}
}
@@ -118,47 +118,60 @@ public class Engine {
/**
* Creates a Engine object
*
- * @param service
+ * @param serviceName
*/
- public Engine(String service) {
- this.serviceName = service;
+ public Engine(String serviceName) {
+ this.serviceName = serviceName;
}
/**
* Finds the appropriate service implementation and returns an
- * {@code SpiAndProvider} instance containing a reference to SPI
- * and its {@code Provider}
+ * {@code SpiAndProvider} instance containing a reference to the first
+ * matching SPI and its {@code Provider}
*/
public SpiAndProvider getInstance(String algorithm, Object param)
throws NoSuchAlgorithmException {
if (algorithm == null) {
throw new NoSuchAlgorithmException("Null algorithm name");
}
+ ArrayList<Provider.Service> services = getServices(algorithm);
+ if (services == null) {
+ throw notFound(this.serviceName, algorithm);
+ }
+ return new SpiAndProvider(services.get(0).newInstance(param), services.get(0).getProvider());
+ }
+
+ /**
+ * Finds the appropriate service implementation and returns an
+ * {@code SpiAndProvider} instance containing a reference to SPI
+ * and its {@code Provider}
+ */
+ public SpiAndProvider getInstance(Provider.Service service, String param)
+ throws NoSuchAlgorithmException {
+ return new SpiAndProvider(service.newInstance(param), service.getProvider());
+ }
+
+ /**
+ * Returns a list of all possible matches for a given algorithm.
+ */
+ public ArrayList<Provider.Service> getServices(String algorithm) {
int newCacheVersion = Services.getCacheVersion();
- Provider.Service service;
ServiceCacheEntry cacheEntry = this.serviceCache;
+ final String algoUC = algorithm.toUpperCase(Locale.US);
if (cacheEntry != null
- && cacheEntry.algorithm.equalsIgnoreCase(algorithm)
+ && cacheEntry.algorithm.equalsIgnoreCase(algoUC)
&& newCacheVersion == cacheEntry.cacheVersion) {
- service = cacheEntry.service;
- } else {
- if (Services.isEmpty()) {
- throw notFound(serviceName, algorithm);
- }
- String name = this.serviceName + "." + algorithm.toUpperCase(Locale.US);
- service = Services.getService(name);
- if (service == null) {
- throw notFound(serviceName, algorithm);
- }
- this.serviceCache = new ServiceCacheEntry(algorithm, newCacheVersion, service);
+ return cacheEntry.services;
}
- return new SpiAndProvider(service.newInstance(param), service.getProvider());
+ String name = this.serviceName + "." + algoUC;
+ ArrayList<Provider.Service> services = Services.getServices(name);
+ this.serviceCache = new ServiceCacheEntry(algoUC, newCacheVersion, services);
+ return services;
}
/**
- * Finds the appropriate service implementation and returns and
- * instance of the class that implements corresponding Service
- * Provider Interface.
+ * Finds the appropriate service implementation and returns and instance of
+ * the class that implements corresponding Service Provider Interface.
*/
public Object getInstance(String algorithm, Provider provider, Object param)
throws NoSuchAlgorithmException {
diff --git a/luni/src/main/java/org/apache/harmony/security/fortress/Services.java b/luni/src/main/java/org/apache/harmony/security/fortress/Services.java
index 4fe0d44..30f4839 100644
--- a/luni/src/main/java/org/apache/harmony/security/fortress/Services.java
+++ b/luni/src/main/java/org/apache/harmony/security/fortress/Services.java
@@ -21,9 +21,7 @@ import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Locale;
-import java.util.Map;
/**
@@ -38,8 +36,8 @@ public class Services {
* Set the initial size to 600 so we don't grow to 1024 by default because
* initialization adds a few entries more than the growth threshold.
*/
- private static final Map<String, Provider.Service> services
- = new HashMap<String, Provider.Service>(600);
+ private static final HashMap<String, ArrayList<Provider.Service>> services
+ = new HashMap<String, ArrayList<Provider.Service>>(600);
/**
* Save default SecureRandom service as well.
@@ -62,12 +60,13 @@ public class Services {
/**
* Registered providers.
*/
- private static final List<Provider> providers = new ArrayList<Provider>(20);
+ private static final ArrayList<Provider> providers = new ArrayList<Provider>(20);
/**
* Hash for quick provider access by name.
*/
- private static final Map<String, Provider> providersNames = new HashMap<String, Provider>(20);
+ private static final HashMap<String, Provider> providersNames
+ = new HashMap<String, Provider>(20);
static {
String providerClassName = null;
int i = 1;
@@ -75,7 +74,7 @@ public class Services {
while ((providerClassName = Security.getProperty("security.provider." + i++)) != null) {
try {
- Class providerClass = Class.forName(providerClassName.trim(), true, cl);
+ Class<?> providerClass = Class.forName(providerClassName.trim(), true, cl);
Provider p = (Provider) providerClass.newInstance();
providers.add(p);
providersNames.put(p.getName(), p);
@@ -91,15 +90,8 @@ public class Services {
/**
* Returns a copy of the registered providers as an array.
*/
- public static synchronized Provider[] getProviders() {
- return providers.toArray(new Provider[providers.size()]);
- }
-
- /**
- * Returns a copy of the registered providers as a list.
- */
- public static synchronized List<Provider> getProvidersList() {
- return new ArrayList<Provider>(providers);
+ public static synchronized ArrayList<Provider> getProviders() {
+ return providers;
}
/**
@@ -145,20 +137,28 @@ public class Services {
cachedSecureRandomService = service;
}
String key = type + "." + service.getAlgorithm().toUpperCase(Locale.US);
- if (!services.containsKey(key)) {
- services.put(key, service);
- }
+ appendServiceLocked(key, service);
for (String alias : Engine.door.getAliases(service)) {
key = type + "." + alias.toUpperCase(Locale.US);
- if (!services.containsKey(key)) {
- services.put(key, service);
- }
+ appendServiceLocked(key, service);
}
}
}
/**
- * Returns true if services contain any provider information.
+ * Add or append the service to the key.
+ */
+ private static void appendServiceLocked(String key, Provider.Service service) {
+ ArrayList<Provider.Service> serviceList = services.get(key);
+ if (serviceList == null) {
+ serviceList = new ArrayList<Provider.Service>(1);
+ services.put(key, serviceList);
+ }
+ serviceList.add(service);
+ }
+
+ /**
+ * Returns true if services does not contain any provider information.
*/
public static synchronized boolean isEmpty() {
return services.isEmpty();
@@ -174,7 +174,7 @@ public class Services {
* caches should be validated against the result of
* Service.getCacheVersion() before use.
*/
- public static synchronized Provider.Service getService(String key) {
+ public static synchronized ArrayList<Provider.Service> getServices(String key) {
return services.get(key);
}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/Cache.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/Cache.java
deleted file mode 100644
index a2c5b4c..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/Cache.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
-* @author Alexander Y. Kleymenov
-* @version $Revision$
-*/
-
-package org.apache.harmony.security.provider.cert;
-
-import java.util.Arrays;
-
-/**
- * The caching mechanism designed to speed up the process
- * of Certificates/CRLs generation in the case of their repeated
- * generation.
- *
- * It keeps correspondences between Objects (Certificates or CLRs)
- * and arrays of bytes on the base of which the Objects have been generated,
- * and provides the means to determine whether it contains the object built on
- * the base of particular encoded form or not. If there are such
- * objects they are returned from the cache, if not - newly generated
- * objects can be saved in the cache.<br>
- *
- * The process of Certificate/CRL generation
- * (implemented in <code>X509CertFactoryImpl</code>) is accompanied with
- * prereading of the beginning of encoded form. This prefix is used to determine
- * whether provided form is PEM encoding or not.<br>
- *
- * So the use of the prefix is the first point to (approximately)
- * determine whether object to be generated is in the cache or not.
- *
- * The failure of the predetermination process tells us that there were not
- * object generated from the encoded form with such prefix and we should
- * generate (decode) the object. If predetermination is successful,
- * we conduct the accurate search on the base of whole encoded form. <br>
- *
- * So to speed up the object generation process this caching mechanism provides
- * the following functionality:<br>
- *
- * 1. With having of the beginning of the encoded form (prefix)
- * it is possible to predetermine whether object has already been
- * generated on the base of the encoding with the SIMILAR prefix or not.
- * This process is not computationally expensive and takes a little time.
- * But it prevents us from use of expensive full encoding
- * search in the case of its failure.<br>
- *
- * 2. If predetermination ends with success, the whole encoding
- * form should be provided to make the final answer: whether object has
- * already been generated on the base of this PARTICULAR encoded form or not.
- * If it is so - the cached object is returned from the cache,
- * if not - new object should be generated and saved in the cache.<br>
- *
- * Note: The length of the prefixes of the encoded forms should not be
- * less than correspondence (default value is 28).
- */
-public class Cache {
-
- // Hash code consist of 6 bytes: AABB00
- // where:
- // AA - 2 bytes for prefix hash
- // value generated on the base of the prefix of encoding
- // BB - 2 bytes for tail hash
- // value generated on the base of the tail of encoding
- // 00 - 2 reserved bytes equals to 0
- //
- // Note, that it is possible for 2 different arrays to have
- // the similar hash codes.
-
- // The masks to work with hash codes:
- // the hash code without the reserved bytes
- private static final long HASH_MASK = 0xFFFFFFFFFFFF0000L;
- // the hash code of the prefix
- private static final long PREFIX_HASH_MASK = 0xFFFFFFFF00000000L;
- // the index value contained in reserved bytes
- private static final int INDEX_MASK = 0x00FFFF;
-
- // size of the cache
- private final int cache_size;
- // the number of bytes which will be used for array hash generation.
- private final int prefix_size;
-
- // The following 3 arrays contain the information about cached objects.
- // This information includes: hash of the array, encoded form of the object,
- // and the object itself.
- // The hash-encoding-object correspondence is made by means of index
- // in the particular array. I.e. for index N hash contained in hashes[N]
- // corresponds to the encoding contained in encodings[N] which corresponds
- // to the object cached at cache[N]
-
- // array containing the hash codes of encodings
- private final long[] hashes;
- // array containing the encodings of the cached objects
- private final byte[][] encodings;
- // array containing the cached objects
- private final Object[] cache;
-
- // This array is used to speed up the process of the search in the cache.
- // This is an ordered array of the hash codes from 'hashes' array (described
- // above) with last 2 (reserved) bytes equals to the index of
- // the hash in the 'hashes' array. I.e. hash code ABCD00 with index 10 in
- // the hashes array will be represented in this array as ABCD0A (10==0x0A)
- // So this array contains ordered <hash to index> correspondences.
- // Note, that every item in this array is unique.
- private final long[] hashes_idx;
-
- // the index of the last cached object
- private int last_cached = 0;
- // cache population indicator
- private boolean cache_is_full = false;
-
- /**
- * Creates the Cache object.
- * @param pref_size specifies how many leading/trailing bytes of object's
- * encoded form will be used for hash computation
- * @param size capacity of the cache to be created.
- */
- public Cache(int pref_size, int size) {
- cache_size = size;
- prefix_size = pref_size;
- hashes = new long[cache_size];
- hashes_idx = new long[cache_size];
- encodings = new byte[cache_size][];
- cache = new Object[cache_size];
- }
-
- /**
- * Creates the Cache object of size of 9.
- * @param pref_size specifies how many leading/trailing bytes of object's
- * encoded form will be used for hash computation
- */
- public Cache(int pref_size) {
- this(pref_size, 9);
- }
-
- /**
- * Creates the Cache object of size of 9.
- */
- public Cache() {
- this(28, 9);
- }
-
- /**
- * Returns the hash code for the array. This code is used to
- * predetermine whether the object was built on the base of the
- * similar encoding or not (by means of <code>contains(long)</code> method),
- * to exactly determine whether object is contained in the cache or not,
- * and to put the object in the cache.
- * Note: parameter array should be of length not less than
- * specified by <code>prefix_size</code> (default 28)
- * @param arr the byte array containing at least prefix_size leading bytes
- * of the encoding.
- * @return hash code for specified encoding prefix
- */
- public long getHash(byte[] arr) {
- long hash = 0;
- for (int i=1; i<prefix_size; i++) {
- hash += (arr[i] & 0xFF);
- } // it takes about 2 bytes for prefix_size == 28
-
- // shift to the correct place
- hash = hash << 32;
- return hash;
- }
-
- /**
- * Checks if there are any object in the cache generated
- * on the base of encoding with prefix corresponding
- * to the specified hash code.
- * @param prefix_hash the hash code for the prefix
- * of the encoding (retrieved by method <code>getHash(byte[]))</code>
- * @return false if there were not any object generated
- * on the base of encoding with specified hash code, true
- * otherwise.
- */
- public boolean contains(long prefix_hash) {
- if (prefix_hash == 0) {
- return false;
- }
- int idx = -1*Arrays.binarySearch(hashes_idx, prefix_hash)-1;
- if (idx == cache_size) {
- return false;
- } else {
- return (hashes_idx[idx] & PREFIX_HASH_MASK) == prefix_hash;
- }
- }
-
- /**
- * Returns the object built on the base on the specified encoded
- * form if it is contained in the cache and null otherwise.
- * This method is computationally expensive and should be called only if
- * the method <code>contains(long)</code> for the hash code returned true.
- * @param hash the hash code for the prefix of the encoding
- * (retrieved by method <code>getHash(byte[])</code>)
- * @param encoding encoded form of the required object.
- * @return the object corresponding to specified encoding or null if
- * there is no such correspondence.
- */
- public Object get(long hash, byte[] encoding) {
- hash |= getSuffHash(encoding);
- if (hash == 0) {
- return null;
- }
- int idx = -1*Arrays.binarySearch(hashes_idx, hash)-1;
- if (idx == cache_size) {
- return null;
- }
- while ((hashes_idx[idx] & HASH_MASK) == hash) {
- int i = (int) (hashes_idx[idx] & INDEX_MASK) - 1;
- if (Arrays.equals(encoding, encodings[i])) {
- return cache[i];
- }
- idx++;
- if (idx == cache_size) {
- return null;
- }
- }
- return null;
- }
-
- /**
- * Puts the object into the cache.
- * @param hash hash code for the prefix of the encoding
- * @param encoding the encoded form of the object
- * @param object the object to be saved in the cache
- */
- public void put(long hash, byte[] encoding, Object object) {
- // check for empty space in the cache
- if (last_cached == cache_size) {
- // so cache is full, will erase the first entry in the
- // cache (oldest entry). it could be better to throw out
- // rarely used value instead of oldest one..
- last_cached = 0;
- cache_is_full = true;
- }
- // index pointing to the item of the table to be overwritten
- int index = last_cached++;
-
- // improve the hash value with info from the tail of encoding
- hash |= getSuffHash(encoding);
-
- if (cache_is_full) {
- // indexing hash value to be overwritten:
- long idx_hash = (hashes[index] | (index+1));
- int idx = Arrays.binarySearch(hashes_idx, idx_hash);
- if (idx < 0) {
- // it will never happen because we use saved hash value
- // (hashes[index])
- System.out.println("WARNING! "+idx);
- idx = -(idx + 1);
- }
- long new_hash_idx = (hash | (index + 1));
- int new_idx = Arrays.binarySearch(hashes_idx, new_hash_idx);
- if (new_idx >= 0) {
- // it's possible when we write the same hash in the same cell
- if (idx != new_idx) {
- // it will never happen because we use the same
- // hash and the same index in hash table
- System.out.println("WARNING: ");
- System.out.println(">> idx: "+idx+" new_idx: "+new_idx);
- }
- } else {
- new_idx = -(new_idx + 1);
- // replace in sorted array
- if (new_idx > idx) {
- System.arraycopy(hashes_idx, idx+1, hashes_idx, idx,
- new_idx - idx - 1);
- hashes_idx[new_idx-1] = new_hash_idx;
- } else if (idx > new_idx) {
- System.arraycopy(hashes_idx, new_idx, hashes_idx, new_idx+1,
- idx - new_idx);
- hashes_idx[new_idx] = new_hash_idx;
- } else { // idx == new_idx
- hashes_idx[new_idx] = new_hash_idx;
- }
- }
- } else {
- long idx_hash = (hash | (index + 1));
- int idx = Arrays.binarySearch(hashes_idx, idx_hash);
- if (idx < 0) {
- // it will always be true because idx_hash depends on index
- idx = -(idx + 1);
- }
- idx = idx - 1;
- if (idx != cache_size - index - 1) {
- // if not in the cell containing 0 (free cell), do copy:
- System.arraycopy(hashes_idx, cache_size - index,
- hashes_idx, cache_size - index - 1,
- idx - (cache_size - index) + 1);
- }
- hashes_idx[idx] = idx_hash;
- }
- // overwrite the values in the tables:
- hashes[index] = hash;
- encodings[index] = encoding;
- cache[index] = object;
- }
-
- // Returns the hash code built on the base of the tail of the encoded form
- // @param arr - the array containing at least prefix_size trailing bytes
- // of encoded form
- private long getSuffHash(byte[] arr) {
- long hash_addon = 0;
- for (int i=arr.length-1; i>arr.length - prefix_size; i--) {
- hash_addon += (arr[i] & 0xFF);
- }
- return hash_addon << 16;
- }
-
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/DRLCertFactory.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/DRLCertFactory.java
deleted file mode 100644
index 790be67..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/DRLCertFactory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
-* @author Alexander Y. Kleymenov
-* @version $Revision$
-*/
-
-package org.apache.harmony.security.provider.cert;
-
-import java.security.Provider;
-
-public final class DRLCertFactory extends Provider {
- /**
- * @serial
- */
- private static final long serialVersionUID = -7269650779605195879L;
-
- /**
- * Constructs the instance of the certificate factory provider.
- */
- public DRLCertFactory() {
- // specification of the provider name, version, and description.
- super("DRLCertFactory", 1.0, "ASN.1, DER, PkiPath, PKCS7");
- // register the service
- put("CertificateFactory.X509", "org.apache.harmony.security.provider.cert.X509CertFactoryImpl");
- // mapping the alias
- put("Alg.Alias.CertificateFactory.X.509", "X509");
- }
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLEntryImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLEntryImpl.java
deleted file mode 100644
index 38500e5..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLEntryImpl.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
-* @author Alexander Y. Kleymenov
-* @version $Revision$
-*/
-
-package org.apache.harmony.security.provider.cert;
-
-import java.math.BigInteger;
-import java.security.cert.CRLException;
-import java.security.cert.X509CRLEntry;
-import java.util.Date;
-import java.util.Set;
-import javax.security.auth.x500.X500Principal;
-import org.apache.harmony.security.x509.Extension;
-import org.apache.harmony.security.x509.Extensions;
-import org.apache.harmony.security.x509.TBSCertList;
-
-/**
- * Implementation of X509CRLEntry. It wraps the instance
- * of org.apache.harmony.security.x509.TBSCertList.RevokedCertificate
- * obtained during the decoding of TBSCertList substructure
- * of the CertificateList structure which is an X.509 form of CRL.
- * (see RFC 3280 at http://www.ietf.org/rfc/rfc3280.txt)
- * Normally the instances of this class are constructed by involving
- * X509CRLImpl object.
- * @see org.apache.harmony.security.x509.TBSCertList
- * @see org.apache.harmony.security.provider.cert.X509CRLImpl
- * @see java.security.cert.X509CRLEntry
- */
-public class X509CRLEntryImpl extends X509CRLEntry {
-
- // the crl entry object to be wrapped in X509CRLEntry
- private final TBSCertList.RevokedCertificate rcert;
- // the extensions of the entry
- private final Extensions extensions;
- // issuer of the revoked certificate described by this crl entry
- private final X500Principal issuer;
-
- // encoded form of this revoked certificate entry
- private byte[] encoding;
-
- /**
- * Creates an instance on the base of existing
- * <code>TBSCertList.RevokedCertificate</code> object and
- * information about the issuer of revoked certificate.
- * If specified issuer is null, it is supposed that issuer
- * of the revoked certificate is the same as for involving CRL.
- */
- public X509CRLEntryImpl(TBSCertList.RevokedCertificate rcert,
- X500Principal issuer) {
- this.rcert = rcert;
- this.extensions = rcert.getCrlEntryExtensions();
- this.issuer = issuer;
- }
-
- // ---------------------------------------------------------------------
- // ------ java.security.cert.X509CRLEntry method implementations -------
- // ---------------------------------------------------------------------
-
- /**
- * @see java.security.cert.X509CRLEntry#getEncoded()
- * method documentation for more info
- */
- public byte[] getEncoded() throws CRLException {
- if (encoding == null) {
- encoding = rcert.getEncoded();
- }
- byte[] result = new byte[encoding.length];
- System.arraycopy(encoding, 0, result, 0, encoding.length);
- return result;
- }
-
- /**
- * @see java.security.cert.X509CRLEntry#getSerialNumber()
- * method documentation for more info
- */
- public BigInteger getSerialNumber() {
- return rcert.getUserCertificate();
- }
-
- /**
- * @see java.security.cert.X509CRLEntry#getCertificateIssuer()
- * method documentation for more info
- */
- public X500Principal getCertificateIssuer() {
- return issuer;
- }
-
- /**
- * @see java.security.cert.X509CRLEntry#getRevocationDate()
- * method documentation for more info
- */
- public Date getRevocationDate() {
- return rcert.getRevocationDate();
- }
-
- /**
- * @see java.security.cert.X509CRLEntry#hasExtensions()
- * method documentation for more info
- */
- public boolean hasExtensions() {
- return (extensions != null) && (extensions.size() != 0);
- }
-
- /**
- * @see java.security.cert.X509CRLEntry#toString()
- * method documentation for more info
- */
- public String toString() {
- return "X509CRLEntryImpl: "+rcert.toString();
- }
-
- // ---------------------------------------------------------------------
- // ------ java.security.cert.X509Extension method implementations ------
- // ---------------------------------------------------------------------
-
- /**
- * @see java.security.cert.X509Extension#getNonCriticalExtensionOIDs()
- * method documentation for more info
- */
- public Set getNonCriticalExtensionOIDs() {
- if (extensions == null) {
- return null;
- }
- return extensions.getNonCriticalExtensions();
- }
-
- /**
- * @see java.security.cert.X509Extension#getCriticalExtensionOIDs()
- * method documentation for more info
- */
- public Set getCriticalExtensionOIDs() {
- if (extensions == null) {
- return null;
- }
- return extensions.getCriticalExtensions();
- }
-
- /**
- * @see java.security.cert.X509Extension#getExtensionValue(String)
- * method documentation for more info
- */
- public byte[] getExtensionValue(String oid) {
- if (extensions == null) {
- return null;
- }
- Extension ext = extensions.getExtensionByOID(oid);
- return (ext == null) ? null : ext.getRawExtnValue();
- }
-
- /**
- * @see java.security.cert.X509Extension#hasUnsupportedCriticalExtension()
- * method documentation for more info
- */
- public boolean hasUnsupportedCriticalExtension() {
- if (extensions == null) {
- return false;
- }
- return extensions.hasUnsupportedCritical();
- }
-}
-
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLImpl.java
deleted file mode 100644
index 68ec38a..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLImpl.java
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
-* @author Alexander Y. Kleymenov
-* @version $Revision$
-*/
-
-package org.apache.harmony.security.provider.cert;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Principal;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CRLException;
-import java.security.cert.Certificate;
-import java.security.cert.X509CRL;
-import java.security.cert.X509CRLEntry;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import javax.security.auth.x500.X500Principal;
-import org.apache.harmony.security.utils.AlgNameMapper;
-import org.apache.harmony.security.x509.CertificateList;
-import org.apache.harmony.security.x509.Extension;
-import org.apache.harmony.security.x509.Extensions;
-import org.apache.harmony.security.x509.TBSCertList;
-
-/**
- * This class is an implementation of X509CRL. It wraps
- * the instance of org.apache.harmony.security.x509.CertificateList
- * built on the base of provided ASN.1 DER encoded form of
- * CertificateList structure (as specified in RFC 3280
- * http://www.ietf.org/rfc/rfc3280.txt).
- * Implementation supports work with indirect CRLs.
- * @see org.apache.harmony.security.x509.CertificateList
- * @see java.security.cert.X509CRL
- */
-public class X509CRLImpl extends X509CRL {
-
- // the core object to be wrapped in X509CRL
- private final CertificateList crl;
-
- // To speed up access to the info, the following fields
- // cache values retrieved from the CertificateList object
- private final TBSCertList tbsCertList;
- private byte[] tbsCertListEncoding;
- private final Extensions extensions;
- private X500Principal issuer;
- private ArrayList entries;
- private int entriesSize;
- private byte[] signature;
- private String sigAlgOID;
- private String sigAlgName;
- private byte[] sigAlgParams;
-
- // encoded form of crl
- private byte[] encoding;
-
- // indicates whether the signature algorithm parameters are null
- private boolean nullSigAlgParams;
- // indicates whether the crl entries have already been retrieved
- // from CertificateList object (crl)
- private boolean entriesRetrieved;
-
- // indicates whether this X.509 CRL is direct or indirect
- // (see rfc 3280 http://www.ietf.org/rfc/rfc3280.txt, p 5.)
- private boolean isIndirectCRL;
- // if crl is indirect, this field holds an info about how
- // many of the leading certificates in the list are issued
- // by the same issuer as CRL.
- private int nonIndirectEntriesSize;
-
- /**
- * Creates X.509 CRL by wrapping of the specified CertificateList object.
- */
- public X509CRLImpl(CertificateList crl) {
- this.crl = crl;
- this.tbsCertList = crl.getTbsCertList();
- this.extensions = tbsCertList.getCrlExtensions();
- }
-
- /**
- * Creates X.509 CRL on the base of ASN.1 DER encoded form of
- * the CRL (CertificateList structure described in RFC 3280)
- * provided via input stream.
- * @throws CRLException if decoding errors occur.
- */
- public X509CRLImpl(InputStream in) throws CRLException {
- try {
- // decode CertificateList structure
- this.crl = (CertificateList) CertificateList.ASN1.decode(in);
- this.tbsCertList = crl.getTbsCertList();
- this.extensions = tbsCertList.getCrlExtensions();
- } catch (IOException e) {
- throw new CRLException(e);
- }
- }
-
- /**
- * Creates X.509 CRL on the base of ASN.1 DER encoded form of
- * the CRL (CertificateList structure described in RFC 3280)
- * provided via array of bytes.
- * @throws IOException if decoding errors occur.
- */
- public X509CRLImpl(byte[] encoding) throws IOException {
- this((CertificateList) CertificateList.ASN1.decode(encoding));
- }
-
- // ---------------------------------------------------------------------
- // ----- java.security.cert.X509CRL abstract method implementations ----
- // ---------------------------------------------------------------------
-
- /**
- * @see java.security.cert.X509CRL#getEncoded()
- * method documentation for more info
- */
- public byte[] getEncoded() throws CRLException {
- if (encoding == null) {
- encoding = crl.getEncoded();
- }
- byte[] result = new byte[encoding.length];
- System.arraycopy(encoding, 0, result, 0, encoding.length);
- return result;
- }
-
- /**
- * @see java.security.cert.X509CRL#getVersion()
- * method documentation for more info
- */
- public int getVersion() {
- return tbsCertList.getVersion();
- }
-
- /**
- * @see java.security.cert.X509CRL#getIssuerDN()
- * method documentation for more info
- */
- public Principal getIssuerDN() {
- if (issuer == null) {
- issuer = tbsCertList.getIssuer().getX500Principal();
- }
- return issuer;
- }
-
- /**
- * @see java.security.cert.X509CRL#getIssuerX500Principal()
- * method documentation for more info
- */
- public X500Principal getIssuerX500Principal() {
- if (issuer == null) {
- issuer = tbsCertList.getIssuer().getX500Principal();
- }
- return issuer;
- }
-
- /**
- * @see java.security.cert.X509CRL#getThisUpdate()
- * method documentation for more info
- */
- public Date getThisUpdate() {
- return tbsCertList.getThisUpdate();
- }
-
- /**
- * @see java.security.cert.X509CRL#getNextUpdate()
- * method documentation for more info
- */
- public Date getNextUpdate() {
- return tbsCertList.getNextUpdate();
- }
-
- /*
- * Retrieves the crl entries (TBSCertList.RevokedCertificate objects)
- * from the TBSCertList structure and converts them to the
- * X509CRLEntryImpl objects
- */
- private void retrieveEntries() {
- entriesRetrieved = true;
- List rcerts = tbsCertList.getRevokedCertificates();
- if (rcerts == null) {
- return;
- }
- entriesSize = rcerts.size();
- entries = new ArrayList(entriesSize);
- // null means that revoked certificate issuer is the same as CRL issuer
- X500Principal rcertIssuer = null;
- for (int i=0; i<entriesSize; i++) {
- TBSCertList.RevokedCertificate rcert =
- (TBSCertList.RevokedCertificate) rcerts.get(i);
- X500Principal iss = rcert.getIssuer();
- if (iss != null) {
- // certificate issuer differs from CRL issuer
- // and CRL is indirect.
- rcertIssuer = iss;
- isIndirectCRL = true;
- // remember how many leading revoked certificates in the
- // list are issued by the same issuer as issuer of CRL
- // (these certificates are first in the list)
- nonIndirectEntriesSize = i;
- }
- entries.add(new X509CRLEntryImpl(rcert, rcertIssuer));
- }
- }
-
- /**
- * Searches for certificate in CRL.
- * This method supports indirect CRLs: if CRL is indirect method takes
- * into account serial number and issuer of the certificate,
- * if CRL issued by CA (i.e. it is not indirect) search is done only
- * by serial number of the specified certificate.
- * @see java.security.cert.X509CRL#getRevokedCertificate(X509Certificate)
- * method documentation for more info
- */
- public X509CRLEntry getRevokedCertificate(X509Certificate certificate) {
- if (certificate == null) {
- throw new NullPointerException("certificate == null");
- }
- if (!entriesRetrieved) {
- retrieveEntries();
- }
- if (entries == null) {
- return null;
- }
- BigInteger serialN = certificate.getSerialNumber();
- if (isIndirectCRL) {
- // search in indirect crl
- X500Principal certIssuer = certificate.getIssuerX500Principal();
- if (certIssuer.equals(getIssuerX500Principal())) {
- // certificate issuer is CRL issuer
- certIssuer = null;
- }
- for (int i=0; i<entriesSize; i++) {
- X509CRLEntry entry = (X509CRLEntry) entries.get(i);
- // check the serial number of revoked certificate
- if (serialN.equals(entry.getSerialNumber())) {
- // revoked certificate issuer
- X500Principal iss = entry.getCertificateIssuer();
- // check the issuer of revoked certificate
- if (certIssuer != null) {
- // certificate issuer is not a CRL issuer, so
- // check issuers for equality
- if (certIssuer.equals(iss)) {
- return entry;
- }
- } else if (iss == null) {
- // both certificates was issued by CRL issuer
- return entry;
- }
- }
- }
- } else {
- // search in CA's (non indirect) crl: just look up the serial number
- for (int i=0; i<entriesSize; i++) {
- X509CRLEntry entry = (X509CRLEntry) entries.get(i);
- if (serialN.equals(entry.getSerialNumber())) {
- return entry;
- }
- }
- }
- return null;
- }
-
- /**
- * Method searches for CRL entry with specified serial number.
- * The method will search only certificate issued by CRL's issuer.
- * @see java.security.cert.X509CRL#getRevokedCertificate(BigInteger)
- * method documentation for more info
- */
- public X509CRLEntry getRevokedCertificate(BigInteger serialNumber) {
- if (!entriesRetrieved) {
- retrieveEntries();
- }
- if (entries == null) {
- return null;
- }
- for (int i=0; i<nonIndirectEntriesSize; i++) {
- X509CRLEntry entry = (X509CRLEntry) entries.get(i);
- if (serialNumber.equals(entry.getSerialNumber())) {
- return entry;
- }
- }
- return null;
- }
-
- /**
- * @see java.security.cert.X509CRL#getRevokedCertificates()
- * method documentation for more info
- */
- public Set<? extends X509CRLEntry> getRevokedCertificates() {
- if (!entriesRetrieved) {
- retrieveEntries();
- }
- if (entries == null) {
- return null;
- }
- return new HashSet(entries);
- }
-
- /**
- * @see java.security.cert.X509CRL#getTBSCertList()
- * method documentation for more info
- */
- public byte[] getTBSCertList() throws CRLException {
- if (tbsCertListEncoding == null) {
- tbsCertListEncoding = tbsCertList.getEncoded();
- }
- byte[] result = new byte[tbsCertListEncoding.length];
- System.arraycopy(tbsCertListEncoding, 0,
- result, 0, tbsCertListEncoding.length);
- return result;
- }
-
- /**
- * @see java.security.cert.X509CRL#getSignature()
- * method documentation for more info
- */
- public byte[] getSignature() {
- if (signature == null) {
- signature = crl.getSignatureValue();
- }
- byte[] result = new byte[signature.length];
- System.arraycopy(signature, 0, result, 0, signature.length);
- return result;
- }
-
- /**
- * @see java.security.cert.X509CRL#getSigAlgName()
- * method documentation for more info
- */
- public String getSigAlgName() {
- if (sigAlgOID == null) {
- sigAlgOID = tbsCertList.getSignature().getAlgorithm();
- sigAlgName = AlgNameMapper.map2AlgName(sigAlgOID);
- if (sigAlgName == null) {
- sigAlgName = sigAlgOID;
- }
- }
- return sigAlgName;
- }
-
- /**
- * @see java.security.cert.X509CRL#getSigAlgOID()
- * method documentation for more info
- */
- public String getSigAlgOID() {
- if (sigAlgOID == null) {
- sigAlgOID = tbsCertList.getSignature().getAlgorithm();
- sigAlgName = AlgNameMapper.map2AlgName(sigAlgOID);
- if (sigAlgName == null) {
- sigAlgName = sigAlgOID;
- }
- }
- return sigAlgOID;
- }
-
- /**
- * @see java.security.cert.X509CRL#getSigAlgParams()
- * method documentation for more info
- */
- public byte[] getSigAlgParams() {
- if (nullSigAlgParams) {
- return null;
- }
- if (sigAlgParams == null) {
- sigAlgParams = tbsCertList.getSignature().getParameters();
- if (sigAlgParams == null) {
- nullSigAlgParams = true;
- return null;
- }
- }
- return sigAlgParams;
- }
-
- /**
- * @see java.security.cert.X509CRL#verify(PublicKey key)
- * method documentation for more info
- */
- public void verify(PublicKey key)
- throws CRLException, NoSuchAlgorithmException,
- InvalidKeyException, NoSuchProviderException,
- SignatureException {
- Signature signature = Signature.getInstance(getSigAlgName());
- signature.initVerify(key);
- byte[] tbsEncoding = tbsCertList.getEncoded();
- signature.update(tbsEncoding, 0, tbsEncoding.length);
- if (!signature.verify(crl.getSignatureValue())) {
- throw new SignatureException("Signature was not verified");
- }
- }
-
- /**
- * @see java.security.cert.X509CRL#verify(PublicKey key, String sigProvider)
- * method documentation for more info
- */
- public void verify(PublicKey key, String sigProvider)
- throws CRLException, NoSuchAlgorithmException,
- InvalidKeyException, NoSuchProviderException,
- SignatureException {
- Signature signature = Signature.getInstance(
- getSigAlgName(), sigProvider);
- signature.initVerify(key);
- byte[] tbsEncoding = tbsCertList.getEncoded();
- signature.update(tbsEncoding, 0, tbsEncoding.length);
- if (!signature.verify(crl.getSignatureValue())) {
- throw new SignatureException("Signature was not verified");
- }
- }
-
- // ---------------------------------------------------------------------
- // ------ java.security.cert.CRL abstract method implementations -------
- // ---------------------------------------------------------------------
-
- /**
- * @see java.security.cert.CRL#isRevoked(Certificate)
- * method documentation for more info
- */
- public boolean isRevoked(Certificate cert) {
- if (!(cert instanceof X509Certificate)) {
- return false;
- }
- return getRevokedCertificate((X509Certificate) cert) != null;
- }
-
- /**
- * @see java.security.cert.CRL#toString()
- * method documentation for more info
- */
- public String toString() {
- return crl.toString();
- }
-
- // ---------------------------------------------------------------------
- // ------ java.security.cert.X509Extension method implementations ------
- // ---------------------------------------------------------------------
-
- /**
- * @see java.security.cert.X509Extension#getNonCriticalExtensionOIDs()
- * method documentation for more info
- */
- public Set getNonCriticalExtensionOIDs() {
- if (extensions == null) {
- return null;
- }
- return extensions.getNonCriticalExtensions();
- }
-
- /**
- * @see java.security.cert.X509Extension#getCriticalExtensionOIDs()
- * method documentation for more info
- */
- public Set getCriticalExtensionOIDs() {
- if (extensions == null) {
- return null;
- }
- return extensions.getCriticalExtensions();
- }
-
- /**
- * @see java.security.cert.X509Extension#getExtensionValue(String)
- * method documentation for more info
- */
- public byte[] getExtensionValue(String oid) {
- if (extensions == null) {
- return null;
- }
- Extension ext = extensions.getExtensionByOID(oid);
- return (ext == null) ? null : ext.getRawExtnValue();
- }
-
- /**
- * @see java.security.cert.X509Extension#hasUnsupportedCriticalExtension()
- * method documentation for more info
- */
- public boolean hasUnsupportedCriticalExtension() {
- if (extensions == null) {
- return false;
- }
- return extensions.hasUnsupportedCritical();
- }
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
deleted file mode 100644
index 9129ec2..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
+++ /dev/null
@@ -1,858 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
-* @author Alexander Y. Kleymenov
-* @version $Revision$
-*/
-
-package org.apache.harmony.security.provider.cert;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.security.cert.CRL;
-import java.security.cert.CRLException;
-import java.security.cert.CertPath;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactorySpi;
-import java.security.cert.X509CRL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import libcore.io.Base64;
-import libcore.io.Streams;
-import org.apache.harmony.security.asn1.ASN1Constants;
-import org.apache.harmony.security.asn1.BerInputStream;
-import org.apache.harmony.security.pkcs7.ContentInfo;
-import org.apache.harmony.security.pkcs7.SignedData;
-import org.apache.harmony.security.x509.CertificateList;
-
-/**
- * X509 Certificate Factory Service Provider Interface Implementation.
- * It supports CRLs and Certificates in (PEM) ASN.1 DER encoded form,
- * and Certification Paths in PkiPath and PKCS7 formats.
- * For Certificates and CRLs factory maintains the caching
- * mechanisms allowing to speed up repeated Certificate/CRL
- * generation.
- * @see Cache
- */
-public class X509CertFactoryImpl extends CertificateFactorySpi {
-
- // number of leading/trailing bytes used for cert hash computation
- private static final int CERT_CACHE_SEED_LENGTH = 28;
- // certificate cache
- private static final Cache CERT_CACHE = new Cache(CERT_CACHE_SEED_LENGTH);
- // number of leading/trailing bytes used for crl hash computation
- private static final int CRL_CACHE_SEED_LENGTH = 24;
- // crl cache
- private static final Cache CRL_CACHE = new Cache(CRL_CACHE_SEED_LENGTH);
-
- /**
- * Default constructor.
- * Creates the instance of Certificate Factory SPI ready for use.
- */
- public X509CertFactoryImpl() { }
-
- /**
- * Generates the X.509 certificate from the data in the stream.
- * The data in the stream can be either in ASN.1 DER encoded X.509
- * certificate, or PEM (Base64 encoding bounded by
- * <code>"-----BEGIN CERTIFICATE-----"</code> at the beginning and
- * <code>"-----END CERTIFICATE-----"</code> at the end) representation
- * of the former encoded form.
- *
- * Before the generation the encoded form is looked up in
- * the cache. If the cache contains the certificate with requested encoded
- * form it is returned from it, otherwise it is generated by ASN.1
- * decoder.
- *
- * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificate(InputStream)
- * method documentation for more info
- */
- public Certificate engineGenerateCertificate(InputStream inStream)
- throws CertificateException {
- if (inStream == null) {
- throw new CertificateException("inStream == null");
- }
- try {
- if (!inStream.markSupported()) {
- // create the mark supporting wrapper
- inStream = new RestoringInputStream(inStream);
- }
- // mark is needed to recognize the format of the provided encoding
- // (ASN.1 or PEM)
- inStream.mark(1);
- // check whether the provided certificate is in PEM encoded form
- if (inStream.read() == '-') {
- // decode PEM, retrieve CRL
- return getCertificate(decodePEM(inStream, CERT_BOUND_SUFFIX));
- } else {
- inStream.reset();
- // retrieve CRL
- return getCertificate(inStream);
- }
- } catch (IOException e) {
- throw new CertificateException(e);
- }
- }
-
- /**
- * Generates the collection of the certificates on the base of provided
- * via input stream encodings.
- * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificates(InputStream)
- * method documentation for more info
- */
- public Collection<? extends Certificate>
- engineGenerateCertificates(InputStream inStream)
- throws CertificateException {
- if (inStream == null) {
- throw new CertificateException("inStream == null");
- }
- ArrayList<Certificate> result = new ArrayList<Certificate>();
- try {
- if (!inStream.markSupported()) {
- // create the mark supporting wrapper
- inStream = new RestoringInputStream(inStream);
- }
- // if it is PEM encoded form this array will contain the encoding
- // so ((it is PEM) <-> (encoding != null))
- byte[] encoding = null;
- // The following by SEQUENCE ASN.1 tag, used for
- // recognizing the data format
- // (is it PKCS7 ContentInfo structure, X.509 Certificate, or
- // unsupported encoding)
- int second_asn1_tag = -1;
- inStream.mark(1);
- int ch;
- while ((ch = inStream.read()) != -1) {
- // check if it is PEM encoded form
- if (ch == '-') { // beginning of PEM encoding ('-' char)
- // decode PEM chunk and store its content (ASN.1 encoding)
- encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
- } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
- encoding = null;
- inStream.reset();
- // prepare for data format determination
- inStream.mark(CERT_CACHE_SEED_LENGTH);
- } else { // unsupported data
- if (result.size() == 0) {
- throw new CertificateException("Unsupported encoding");
- } else {
- // it can be trailing user data,
- // so keep it in the stream
- inStream.reset();
- return result;
- }
- }
- // Check the data format
- BerInputStream in = (encoding == null)
- ? new BerInputStream(inStream)
- : new BerInputStream(encoding);
- // read the next ASN.1 tag
- second_asn1_tag = in.next(); // inStream position changed
- if (encoding == null) {
- // keep whole structure in the stream
- inStream.reset();
- }
- // check if it is a TBSCertificate structure
- if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
- if (result.size() == 0) {
- // there were not read X.509 Certificates, so
- // break the cycle and check
- // whether it is PKCS7 structure
- break;
- } else {
- // it can be trailing user data,
- // so return what we already read
- return result;
- }
- } else {
- if (encoding == null) {
- result.add(getCertificate(inStream));
- } else {
- result.add(getCertificate(encoding));
- }
- }
- // mark for the next iteration
- inStream.mark(1);
- }
- if (result.size() != 0) {
- // some Certificates have been read
- return result;
- } else if (ch == -1) {
- /* No data in the stream, so return the empty collection. */
- return result;
- }
- // else: check if it is PKCS7
- if (second_asn1_tag == ASN1Constants.TAG_OID) {
- // it is PKCS7 ContentInfo structure, so decode it
- ContentInfo info = (ContentInfo)
- ((encoding != null)
- ? ContentInfo.ASN1.decode(encoding)
- : ContentInfo.ASN1.decode(inStream));
- // retrieve SignedData
- SignedData data = info.getSignedData();
- if (data == null) {
- throw new CertificateException("Invalid PKCS7 data provided");
- }
- List<org.apache.harmony.security.x509.Certificate> certs = data.getCertificates();
- if (certs != null) {
- for (org.apache.harmony.security.x509.Certificate cert : certs) {
- result.add(new X509CertImpl(cert));
- }
- }
- return result;
- }
- // else: Unknown data format
- throw new CertificateException("Unsupported encoding");
- } catch (IOException e) {
- throw new CertificateException(e);
- }
- }
-
- /**
- * @see java.security.cert.CertificateFactorySpi#engineGenerateCRL(InputStream)
- * method documentation for more info
- */
- public CRL engineGenerateCRL(InputStream inStream)
- throws CRLException {
- if (inStream == null) {
- throw new CRLException("inStream == null");
- }
- try {
- if (!inStream.markSupported()) {
- // Create the mark supporting wrapper
- // Mark is needed to recognize the format
- // of provided encoding form (ASN.1 or PEM)
- inStream = new RestoringInputStream(inStream);
- }
- inStream.mark(1);
- // check whether the provided crl is in PEM encoded form
- if (inStream.read() == '-') {
- // decode PEM, retrieve CRL
- return getCRL(decodePEM(inStream, FREE_BOUND_SUFFIX));
- } else {
- inStream.reset();
- // retrieve CRL
- return getCRL(inStream);
- }
- } catch (IOException e) {
- throw new CRLException(e);
- }
- }
-
- /**
- * @see java.security.cert.CertificateFactorySpi#engineGenerateCRLs(InputStream)
- * method documentation for more info
- */
- public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream)
- throws CRLException {
- if (inStream == null) {
- throw new CRLException("inStream == null");
- }
- ArrayList<CRL> result = new ArrayList<CRL>();
- try {
- if (!inStream.markSupported()) {
- inStream = new RestoringInputStream(inStream);
- }
- // if it is PEM encoded form this array will contain the encoding
- // so ((it is PEM) <-> (encoding != null))
- byte[] encoding = null;
- // The following by SEQUENCE ASN.1 tag, used for
- // recognizing the data format
- // (is it PKCS7 ContentInfo structure, X.509 CRL, or
- // unsupported encoding)
- int second_asn1_tag = -1;
- inStream.mark(1);
- int ch;
- while ((ch = inStream.read()) != -1) {
- // check if it is PEM encoded form
- if (ch == '-') { // beginning of PEM encoding ('-' char)
- // decode PEM chunk and store its content (ASN.1 encoding)
- encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
- } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
- encoding = null;
- inStream.reset();
- // prepare for data format determination
- inStream.mark(CRL_CACHE_SEED_LENGTH);
- } else { // unsupported data
- if (result.size() == 0) {
- throw new CRLException("Unsupported encoding");
- } else {
- // it can be trailing user data,
- // so keep it in the stream
- inStream.reset();
- return result;
- }
- }
- // Check the data format
- BerInputStream in = (encoding == null)
- ? new BerInputStream(inStream)
- : new BerInputStream(encoding);
- // read the next ASN.1 tag
- second_asn1_tag = in.next();
- if (encoding == null) {
- // keep whole structure in the stream
- inStream.reset();
- }
- // check if it is a TBSCertList structure
- if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
- if (result.size() == 0) {
- // there were not read X.509 CRLs, so
- // break the cycle and check
- // whether it is PKCS7 structure
- break;
- } else {
- // it can be trailing user data,
- // so return what we already read
- return result;
- }
- } else {
- if (encoding == null) {
- result.add(getCRL(inStream));
- } else {
- result.add(getCRL(encoding));
- }
- }
- inStream.mark(1);
- }
- if (result.size() != 0) {
- // the stream was read out
- return result;
- } else if (ch == -1) {
- throw new CRLException("There is no data in the stream");
- }
- // else: check if it is PKCS7
- if (second_asn1_tag == ASN1Constants.TAG_OID) {
- // it is PKCS7 ContentInfo structure, so decode it
- ContentInfo info = (ContentInfo)
- ((encoding != null)
- ? ContentInfo.ASN1.decode(encoding)
- : ContentInfo.ASN1.decode(inStream));
- // retrieve SignedData
- SignedData data = info.getSignedData();
- if (data == null) {
- throw new CRLException("Invalid PKCS7 data provided");
- }
- List<CertificateList> crls = data.getCRLs();
- if (crls != null) {
- for (CertificateList crl : crls) {
- result.add(new X509CRLImpl(crl));
- }
- }
- return result;
- }
- // else: Unknown data format
- throw new CRLException("Unsupported encoding");
- } catch (IOException e) {
- throw new CRLException(e);
- }
- }
-
- /**
- * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream)
- * method documentation for more info
- */
- public CertPath engineGenerateCertPath(InputStream inStream)
- throws CertificateException {
- if (inStream == null) {
- throw new CertificateException("inStream == null");
- }
- return engineGenerateCertPath(inStream, "PkiPath");
- }
-
- /**
- * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream,String)
- * method documentation for more info
- */
- public CertPath engineGenerateCertPath(
- InputStream inStream, String encoding) throws CertificateException {
- if (inStream == null) {
- throw new CertificateException("inStream == null");
- }
- if (!inStream.markSupported()) {
- inStream = new RestoringInputStream(inStream);
- }
- try {
- inStream.mark(1);
- int ch;
-
- // check if it is PEM encoded form
- if ((ch = inStream.read()) == '-') {
- // decode PEM chunk into ASN.1 form and decode CertPath object
- return X509CertPathImpl.getInstance(
- decodePEM(inStream, FREE_BOUND_SUFFIX), encoding);
- } else if (ch == 0x30) { // ASN.1 Sequence
- inStream.reset();
- // decode ASN.1 form
- return X509CertPathImpl.getInstance(inStream, encoding);
- } else {
- throw new CertificateException("Unsupported encoding");
- }
- } catch (IOException e) {
- throw new CertificateException(e);
- }
- }
-
- /**
- * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(List)
- * method documentation for more info
- */
- public CertPath engineGenerateCertPath(List<? extends Certificate> certificates)
- throws CertificateException {
- return new X509CertPathImpl(certificates);
- }
-
- /**
- * @see java.security.cert.CertificateFactorySpi#engineGetCertPathEncodings()
- * method documentation for more info
- */
- public Iterator<String> engineGetCertPathEncodings() {
- return X509CertPathImpl.encodings.iterator();
- }
-
- // ---------------------------------------------------------------------
- // ------------------------ Staff methods ------------------------------
- // ---------------------------------------------------------------------
-
- private static final byte[] PEM_BEGIN = "-----BEGIN".getBytes(StandardCharsets.UTF_8);
- private static final byte[] PEM_END = "-----END".getBytes(StandardCharsets.UTF_8);
- /**
- * Code describing free format for PEM boundary suffix:
- * "^-----BEGIN.*\n" at the beginning, and<br>
- * "\n-----END.*(EOF|\n)$" at the end.
- */
- private static final byte[] FREE_BOUND_SUFFIX = null;
- /**
- * Code describing PEM boundary suffix for X.509 certificate:
- * "^-----BEGIN CERTIFICATE-----\n" at the beginning, and<br>
- * "\n-----END CERTIFICATE-----" at the end.
- */
- private static final byte[] CERT_BOUND_SUFFIX = " CERTIFICATE-----".getBytes(StandardCharsets.UTF_8);
-
- /**
- * Method retrieves the PEM encoded data from the stream
- * and returns its decoded representation.
- * Method checks correctness of PEM boundaries. It supposes that
- * the first '-' of the opening boundary has already been read from
- * the stream. So first of all it checks that the leading bytes
- * are equal to "-----BEGIN" boundary prefix. Than if boundary_suffix
- * is not null, it checks that next bytes equal to boundary_suffix
- * + new line char[s] ([CR]LF).
- * If boundary_suffix parameter is null, method supposes free suffix
- * format and skips any bytes until the new line.<br>
- * After the opening boundary has been read and checked, the method
- * read Base64 encoded data until closing PEM boundary is not reached.<br>
- * Than it checks closing boundary - it should start with new line +
- * "-----END" + boundary_suffix. If boundary_suffix is null,
- * any characters are skipped until the new line.<br>
- * After this any trailing new line characters are skipped from the stream,
- * Base64 encoding is decoded and returned.
- * @param inStream the stream containing the PEM encoding.
- * @param boundary_suffix the suffix of expected PEM multipart
- * boundary delimiter.<br>
- * If it is null, that any character sequences are accepted.
- * @throws IOException If PEM boundary delimiter does not comply
- * with expected or some I/O or decoding problems occur.
- */
- private byte[] decodePEM(InputStream inStream, byte[] boundary_suffix)
- throws IOException {
- int ch; // the char to be read
- // check and skip opening boundary delimiter
- // (first '-' is supposed as already read)
- for (int i = 1; i < PEM_BEGIN.length; ++i) {
- if (PEM_BEGIN[i] != (ch = inStream.read())) {
- throw new IOException(
- "Incorrect PEM encoding: '-----BEGIN"
- + ((boundary_suffix == null)
- ? "" : new String(boundary_suffix))
- + "' is expected as opening delimiter boundary.");
- }
- }
- if (boundary_suffix == null) {
- // read (skip) the trailing characters of
- // the beginning PEM boundary delimiter
- while ((ch = inStream.read()) != '\n') {
- if (ch == -1) {
- throw new IOException("Incorrect PEM encoding: EOF before content");
- }
- }
- } else {
- for (int i=0; i<boundary_suffix.length; i++) {
- if (boundary_suffix[i] != inStream.read()) {
- throw new IOException("Incorrect PEM encoding: '-----BEGIN" +
- new String(boundary_suffix) + "' is expected as opening delimiter boundary.");
- }
- }
- // read new line characters
- if ((ch = inStream.read()) == '\r') {
- // CR has been read, now read LF character
- ch = inStream.read();
- }
- if (ch != '\n') {
- throw new IOException("Incorrect PEM encoding: newline expected after " +
- "opening delimiter boundary");
- }
- }
- int size = 1024; // the size of the buffer containing Base64 data
- byte[] buff = new byte[size];
- int index = 0;
- // read bytes while ending boundary delimiter is not reached
- while ((ch = inStream.read()) != '-') {
- if (ch == -1) {
- throw new IOException("Incorrect Base64 encoding: EOF without closing delimiter");
- }
- buff[index++] = (byte) ch;
- if (index == size) {
- // enlarge the buffer
- byte[] newbuff = new byte[size+1024];
- System.arraycopy(buff, 0, newbuff, 0, size);
- buff = newbuff;
- size += 1024;
- }
- }
- if (buff[index-1] != '\n') {
- throw new IOException("Incorrect Base64 encoding: newline expected before " +
- "closing boundary delimiter");
- }
- // check and skip closing boundary delimiter prefix
- // (first '-' was read)
- for (int i = 1; i < PEM_END.length; ++i) {
- if (PEM_END[i] != inStream.read()) {
- throw badEnd(boundary_suffix);
- }
- }
- if (boundary_suffix == null) {
- // read (skip) the trailing characters of
- // the closing PEM boundary delimiter
- while (((ch = inStream.read()) != -1) && (ch != '\n') && (ch != '\r')) {
- }
- } else {
- for (int i=0; i<boundary_suffix.length; i++) {
- if (boundary_suffix[i] != inStream.read()) {
- throw badEnd(boundary_suffix);
- }
- }
- }
- // skip trailing line breaks
- inStream.mark(1);
- while (((ch = inStream.read()) != -1) && (ch == '\n' || ch == '\r')) {
- inStream.mark(1);
- }
- inStream.reset();
- buff = Base64.decode(buff, index);
- if (buff == null) {
- throw new IOException("Incorrect Base64 encoding");
- }
- return buff;
- }
-
- private IOException badEnd(byte[] boundary_suffix) throws IOException {
- String s = (boundary_suffix == null) ? "" : new String(boundary_suffix);
- throw new IOException("Incorrect PEM encoding: '-----END" + s + "' is expected as closing delimiter boundary.");
- }
-
- /**
- * Reads the data of specified length from source
- * and returns it as an array.
- * @return the byte array contained read data or
- * null if the stream contains not enough data
- * @throws IOException if some I/O error has been occurred.
- */
- private static byte[] readBytes(InputStream source, int length)
- throws IOException {
- byte[] result = new byte[length];
- for (int i=0; i<length; i++) {
- int bytik = source.read();
- if (bytik == -1) {
- return null;
- }
- result[i] = (byte) bytik;
- }
- return result;
- }
-
- /**
- * Returns the Certificate object corresponding to the provided encoding.
- * Resulting object is retrieved from the cache
- * if it contains such correspondence
- * and is constructed on the base of encoding
- * and stored in the cache otherwise.
- * @throws IOException if some decoding errors occur
- * (in the case of cache miss).
- */
- private static Certificate getCertificate(byte[] encoding)
- throws CertificateException, IOException {
- if (encoding.length < CERT_CACHE_SEED_LENGTH) {
- throw new CertificateException("encoding.length < CERT_CACHE_SEED_LENGTH");
- }
- synchronized (CERT_CACHE) {
- long hash = CERT_CACHE.getHash(encoding);
- if (CERT_CACHE.contains(hash)) {
- Certificate res =
- (Certificate) CERT_CACHE.get(hash, encoding);
- if (res != null) {
- return res;
- }
- }
- Certificate res = new X509CertImpl(encoding);
- CERT_CACHE.put(hash, encoding, res);
- return res;
- }
- }
-
- /**
- * Returns the Certificate object corresponding to the encoding provided
- * by the stream.
- * Resulting object is retrieved from the cache
- * if it contains such correspondence
- * and is constructed on the base of encoding
- * and stored in the cache otherwise.
- * @throws IOException if some decoding errors occur
- * (in the case of cache miss).
- */
- private static Certificate getCertificate(InputStream inStream)
- throws CertificateException, IOException {
- synchronized (CERT_CACHE) {
- inStream.mark(CERT_CACHE_SEED_LENGTH);
- // read the prefix of the encoding
- byte[] buff = readBytes(inStream, CERT_CACHE_SEED_LENGTH);
- inStream.reset();
- if (buff == null) {
- throw new CertificateException("InputStream doesn't contain enough data");
- }
- long hash = CERT_CACHE.getHash(buff);
- if (CERT_CACHE.contains(hash)) {
- byte[] encoding = new byte[BerInputStream.getLength(buff)];
- if (encoding.length < CERT_CACHE_SEED_LENGTH) {
- throw new CertificateException("Bad Certificate encoding");
- }
- Streams.readFully(inStream, encoding);
- Certificate res = (Certificate) CERT_CACHE.get(hash, encoding);
- if (res != null) {
- return res;
- }
- res = new X509CertImpl(encoding);
- CERT_CACHE.put(hash, encoding, res);
- return res;
- } else {
- inStream.reset();
- Certificate res = new X509CertImpl(inStream);
- CERT_CACHE.put(hash, res.getEncoded(), res);
- return res;
- }
- }
- }
-
- /**
- * Returns the CRL object corresponding to the provided encoding.
- * Resulting object is retrieved from the cache
- * if it contains such correspondence
- * and is constructed on the base of encoding
- * and stored in the cache otherwise.
- * @throws IOException if some decoding errors occur
- * (in the case of cache miss).
- */
- private static CRL getCRL(byte[] encoding)
- throws CRLException, IOException {
- if (encoding.length < CRL_CACHE_SEED_LENGTH) {
- throw new CRLException("encoding.length < CRL_CACHE_SEED_LENGTH");
- }
- synchronized (CRL_CACHE) {
- long hash = CRL_CACHE.getHash(encoding);
- if (CRL_CACHE.contains(hash)) {
- X509CRL res = (X509CRL) CRL_CACHE.get(hash, encoding);
- if (res != null) {
- return res;
- }
- }
- X509CRL res = new X509CRLImpl(encoding);
- CRL_CACHE.put(hash, encoding, res);
- return res;
- }
- }
-
- /**
- * Returns the CRL object corresponding to the encoding provided
- * by the stream.
- * Resulting object is retrieved from the cache
- * if it contains such correspondence
- * and is constructed on the base of encoding
- * and stored in the cache otherwise.
- * @throws IOException if some decoding errors occur
- * (in the case of cache miss).
- */
- private static CRL getCRL(InputStream inStream)
- throws CRLException, IOException {
- synchronized (CRL_CACHE) {
- inStream.mark(CRL_CACHE_SEED_LENGTH);
- byte[] buff = readBytes(inStream, CRL_CACHE_SEED_LENGTH);
- // read the prefix of the encoding
- inStream.reset();
- if (buff == null) {
- throw new CRLException("InputStream doesn't contain enough data");
- }
- long hash = CRL_CACHE.getHash(buff);
- if (CRL_CACHE.contains(hash)) {
- byte[] encoding = new byte[BerInputStream.getLength(buff)];
- if (encoding.length < CRL_CACHE_SEED_LENGTH) {
- throw new CRLException("Bad CRL encoding");
- }
- Streams.readFully(inStream, encoding);
- CRL res = (CRL) CRL_CACHE.get(hash, encoding);
- if (res != null) {
- return res;
- }
- res = new X509CRLImpl(encoding);
- CRL_CACHE.put(hash, encoding, res);
- return res;
- } else {
- X509CRL res = new X509CRLImpl(inStream);
- CRL_CACHE.put(hash, res.getEncoded(), res);
- return res;
- }
- }
- }
-
- /*
- * This class extends any existing input stream with
- * mark functionality. It acts as a wrapper over the
- * stream and supports reset to the
- * marked state with readlimit no more than BUFF_SIZE.
- */
- private static class RestoringInputStream extends InputStream {
-
- // wrapped input stream
- private final InputStream inStream;
- // specifies how much of the read data is buffered
- // after the mark has been set up
- private static final int BUFF_SIZE = 32;
- // buffer to keep the bytes read after the mark has been set up
- private final int[] buff = new int[BUFF_SIZE*2];
- // position of the next byte to read,
- // the value of -1 indicates that the buffer is not used
- // (mark was not set up or was invalidated, or reset to the marked
- // position has been done and all the buffered data was read out)
- private int pos = -1;
- // position of the last buffered byte
- private int bar = 0;
- // position in the buffer where the mark becomes invalidated
- private int end = 0;
-
- /**
- * Creates the mark supporting wrapper over the stream.
- */
- public RestoringInputStream(InputStream inStream) {
- this.inStream = inStream;
- }
-
- @Override
- public int available() throws IOException {
- return (bar - pos) + inStream.available();
- }
-
- @Override
- public void close() throws IOException {
- inStream.close();
- }
-
- @Override
- public void mark(int readlimit) {
- if (pos < 0) {
- pos = 0;
- bar = 0;
- end = BUFF_SIZE - 1;
- } else {
- end = (pos + BUFF_SIZE - 1) % BUFF_SIZE;
- }
- }
-
- @Override
- public boolean markSupported() {
- return true;
- }
-
- /**
- * Reads the byte from the stream. If mark has been set up
- * and was not invalidated byte is read from the underlying
- * stream and saved into the buffer. If the current read position
- * has been reset to the marked position and there are remaining
- * bytes in the buffer, the byte is taken from it. In the other cases
- * (if mark has been invalidated, or there are no buffered bytes)
- * the byte is taken directly from the underlying stream and it is
- * returned without saving to the buffer.
- *
- * @see java.io.InputStream#read()
- * method documentation for more info
- */
- public int read() throws IOException {
- // if buffer is currently used
- if (pos >= 0) {
- // current position in the buffer
- int cur = pos % BUFF_SIZE;
- // check whether the buffer contains the data to be read
- if (cur < bar) {
- // return the data from the buffer
- pos++;
- return buff[cur];
- }
- // check whether buffer has free space
- if (cur != end) {
- // it has, so read the data from the wrapped stream
- // and place it in the buffer
- buff[cur] = inStream.read();
- bar = cur+1;
- pos++;
- return buff[cur];
- } else {
- // buffer if full and can not operate
- // any more, so invalidate the mark position
- // and turn off the using of buffer
- pos = -1;
- }
- }
- // buffer is not used, so return the data from the wrapped stream
- return inStream.read();
- }
-
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- int read_b;
- int i;
- for (i=0; i<len; i++) {
- if ((read_b = read()) == -1) {
- return (i == 0) ? -1 : i;
- }
- b[off+i] = (byte) read_b;
- }
- return i;
- }
-
- @Override
- public void reset() throws IOException {
- if (pos >= 0) {
- pos = (end + 1) % BUFF_SIZE;
- } else {
- throw new IOException("Could not reset the stream: " +
- "position became invalid or stream has not been marked");
- }
- }
- }
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java
deleted file mode 100644
index 4600bdc..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
-* @author Alexander Y. Kleymenov
-* @version $Revision$
-*/
-
-package org.apache.harmony.security.provider.cert;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Principal;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Set;
-import javax.security.auth.x500.X500Principal;
-import org.apache.harmony.security.utils.AlgNameMapper;
-import org.apache.harmony.security.x509.Certificate;
-import org.apache.harmony.security.x509.Extension;
-import org.apache.harmony.security.x509.Extensions;
-import org.apache.harmony.security.x509.TBSCertificate;
-
-/**
- * This class is an implementation of X509Certificate. It wraps
- * the instance of org.apache.harmony.security.x509.Certificate
- * built on the base of provided ASN.1 DER encoded form of
- * Certificate structure (as specified in RFC 3280
- * http://www.ietf.org/rfc/rfc3280.txt).
- * @see org.apache.harmony.security.x509.Certificate
- * @see java.security.cert.X509Certificate
- */
-public final class X509CertImpl extends X509Certificate {
-
- /** @serial */
- private static final long serialVersionUID = 2972248729446736154L;
-
- /** the core object to be wrapped in X509Certificate */
- private final Certificate certificate;
-
- private final TBSCertificate tbsCert;
- private final Extensions extensions;
- // to speed up access to the info, the following fields
- // cache values retrieved from the certificate object,
- // initialized using the "single-check idiom".
- private volatile long notBefore = -1;
- private volatile long notAfter = -1;
- private volatile BigInteger serialNumber;
- private volatile X500Principal issuer;
- private volatile X500Principal subject;
- private volatile byte[] tbsCertificate;
- private volatile byte[] signature;
- private volatile String sigAlgName;
- private volatile String sigAlgOID;
- private volatile byte[] sigAlgParams;
- // indicates whether the signature algorithm parameters are null
- private volatile boolean nullSigAlgParams;
- private volatile PublicKey publicKey;
-
- // encoding of the certificate
- private volatile byte[] encoding;
-
- /**
- * Constructs the instance on the base of ASN.1 encoded
- * form of X.509 certificate provided via stream parameter.
- * @param in input stream containing ASN.1 encoded form of certificate.
- * @throws CertificateException if some decoding problems occur.
- */
- public X509CertImpl(InputStream in) throws CertificateException {
- try {
- // decode the Certificate object
- this.certificate = (Certificate) Certificate.ASN1.decode(in);
- // cache the values of TBSCertificate and Extensions
- this.tbsCert = certificate.getTbsCertificate();
- this.extensions = tbsCert.getExtensions();
- } catch (IOException e) {
- throw new CertificateException(e);
- }
- }
-
- /**
- * Constructs the instance on the base of existing Certificate object to
- * be wrapped.
- */
- public X509CertImpl(Certificate certificate) {
- this.certificate = certificate;
- // cache the values of TBSCertificate and Extensions
- this.tbsCert = certificate.getTbsCertificate();
- this.extensions = tbsCert.getExtensions();
- }
-
- /**
- * Constructs the instance on the base of ASN.1 encoded
- * form of X.509 certificate provided via array of bytes.
- * @param encoding byte array containing ASN.1 encoded form of certificate.
- * @throws IOException if some decoding problems occur.
- */
- public X509CertImpl(byte[] encoding) throws IOException {
- this((Certificate) Certificate.ASN1.decode(encoding));
- }
-
- public void checkValidity()
- throws CertificateExpiredException, CertificateNotYetValidException {
- checkValidity(System.currentTimeMillis());
- }
-
- public void checkValidity(Date date)
- throws CertificateExpiredException, CertificateNotYetValidException {
- checkValidity(date.getTime());
- }
-
- private void checkValidity(long time)
- throws CertificateExpiredException, CertificateNotYetValidException {
- if (time < getNotBeforeInternal()) {
- throw new CertificateNotYetValidException("current time: " + new Date(time)
- + ", validation time: " + new Date(getNotBeforeInternal()));
- }
- if (time > getNotAfterInternal()) {
- throw new CertificateExpiredException("current time: " + new Date(time)
- + ", expiration time: " + new Date(getNotAfterInternal()));
- }
- }
-
- public int getVersion() {
- return tbsCert.getVersion() + 1;
- }
-
- public BigInteger getSerialNumber() {
- BigInteger result = serialNumber;
- if (result == null) {
- serialNumber = result = tbsCert.getSerialNumber();
- }
- return result;
- }
-
- public Principal getIssuerDN() {
- return getIssuerX500Principal();
- }
-
- public X500Principal getIssuerX500Principal() {
- X500Principal result = issuer;
- if (result == null) {
- // retrieve the issuer's principal
- issuer = result = tbsCert.getIssuer().getX500Principal();
- }
- return result;
- }
-
- public Principal getSubjectDN() {
- return getSubjectX500Principal();
- }
-
- public X500Principal getSubjectX500Principal() {
- X500Principal result = subject;
- if (result == null) {
- // retrieve the subject's principal
- subject = result = tbsCert.getSubject().getX500Principal();
- }
- return result;
- }
-
- public Date getNotBefore() {
- return new Date(getNotBeforeInternal());
- }
-
- private long getNotBeforeInternal() {
- long result = notBefore;
- if (result == -1) {
- notBefore = result = tbsCert.getValidity().getNotBefore().getTime();
- }
- return result;
- }
-
- public Date getNotAfter() {
- return new Date(getNotAfterInternal());
- }
-
- private long getNotAfterInternal() {
- long result = notAfter;
- if (result == -1) {
- notAfter = result = tbsCert.getValidity().getNotAfter().getTime();
- }
- return result;
- }
-
- public byte[] getTBSCertificate() throws CertificateEncodingException {
- return getTbsCertificateInternal().clone();
- }
-
- private byte[] getTbsCertificateInternal() {
- byte[] result = tbsCertificate;
- if (result == null) {
- tbsCertificate = result = tbsCert.getEncoded();
- }
- return result;
- }
-
- public byte[] getSignature() {
- return getSignatureInternal().clone();
- }
-
- private byte[] getSignatureInternal() {
- byte[] result = signature;
- if (result == null) {
- signature = result = certificate.getSignatureValue();
- }
- return result;
- }
-
- public String getSigAlgName() {
- String result = sigAlgName;
- if (result == null) {
- String sigAlgOIDLocal = getSigAlgOID();
- // retrieve the name of the signing algorithm
- result = AlgNameMapper.map2AlgName(sigAlgOIDLocal);
- if (result == null) {
- // if could not be found, use OID as a name
- result = sigAlgOIDLocal;
- }
- sigAlgName = result;
- }
- return result;
- }
-
- public String getSigAlgOID() {
- String result = sigAlgOID;
- if (result == null) {
- // if info was not retrieved (and cached), do it:
- sigAlgOID = result = tbsCert.getSignature().getAlgorithm();
- }
- return result;
- }
-
- public byte[] getSigAlgParams() {
- if (nullSigAlgParams) {
- return null;
- }
- byte[] result = sigAlgParams;
- if (result == null) {
- result = tbsCert.getSignature().getParameters();
- if (result == null) {
- nullSigAlgParams = true;
- return null;
- }
- sigAlgParams = result;
- }
- return result;
- }
-
- public boolean[] getIssuerUniqueID() {
- return tbsCert.getIssuerUniqueID();
- }
-
- public boolean[] getSubjectUniqueID() {
- return tbsCert.getSubjectUniqueID();
- }
-
- public boolean[] getKeyUsage() {
- if (extensions == null) {
- return null;
- }
- return extensions.valueOfKeyUsage();
- }
-
- public List<String> getExtendedKeyUsage()
- throws CertificateParsingException {
- if (extensions == null) {
- return null;
- }
- try {
- return extensions.valueOfExtendedKeyUsage();
- } catch (IOException e) {
- throw new CertificateParsingException(e);
- }
- }
-
- public int getBasicConstraints() {
- if (extensions == null) {
- return -1;
- }
- return extensions.valueOfBasicConstraints();
- }
-
- public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
- if (extensions == null) {
- return null;
- }
- try {
- // Retrieve the extension value from the cached extensions object
- // This extension is not checked for correctness during
- // certificate generation, so now it can throw exception
- return extensions.valueOfSubjectAlternativeName();
- } catch (IOException e) {
- throw new CertificateParsingException(e);
- }
- }
-
- /**
- * @see java.security.cert.X509Certificate#getIssuerAlternativeNames()
- * method documentation for more information.
- */
- public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException {
- if (extensions == null) {
- return null;
- }
- try {
- // Retrieve the extension value from the cached extensions object
- // This extension is not checked for correctness during
- // certificate generation, so now it can throw exception
- return extensions.valueOfIssuerAlternativeName();
- } catch (IOException e) {
- throw new CertificateParsingException(e);
- }
- }
-
- @Override public byte[] getEncoded() throws CertificateEncodingException {
- return getEncodedInternal().clone();
- }
- private byte[] getEncodedInternal() throws CertificateEncodingException {
- byte[] result = encoding;
- if (encoding == null) {
- encoding = result = certificate.getEncoded();
- }
- return result;
- }
-
- @Override public PublicKey getPublicKey() {
- PublicKey result = publicKey;
- if (result == null) {
- publicKey = result = tbsCert.getSubjectPublicKeyInfo().getPublicKey();
- }
- return result;
- }
-
- @Override public String toString() {
- return certificate.toString();
- }
-
- @Override public void verify(PublicKey key)
- throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
- NoSuchProviderException, SignatureException {
-
- Signature signature = Signature.getInstance(getSigAlgName());
- signature.initVerify(key);
- // retrieve the encoding of the TBSCertificate structure
- byte[] tbsCertificateLocal = getTbsCertificateInternal();
- // compute and verify the signature
- signature.update(tbsCertificateLocal, 0, tbsCertificateLocal.length);
- if (!signature.verify(certificate.getSignatureValue())) {
- throw new SignatureException("Signature was not verified");
- }
- }
-
- @Override public void verify(PublicKey key, String sigProvider)
- throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
- NoSuchProviderException, SignatureException {
-
- Signature signature = Signature.getInstance(getSigAlgName(), sigProvider);
- signature.initVerify(key);
- // retrieve the encoding of the TBSCertificate structure
- byte[] tbsCertificateLocal = getTbsCertificateInternal();
- // compute and verify the signature
- signature.update(tbsCertificateLocal, 0, tbsCertificateLocal.length);
- if (!signature.verify(certificate.getSignatureValue())) {
- throw new SignatureException("Signature was not verified");
- }
- }
-
- @Override public Set<String> getNonCriticalExtensionOIDs() {
- if (extensions == null) {
- return null;
- }
- // retrieve the info from the cached extensions object
- return extensions.getNonCriticalExtensions();
- }
-
- @Override public Set<String> getCriticalExtensionOIDs() {
- if (extensions == null) {
- return null;
- }
- // retrieve the info from the cached extensions object
- return extensions.getCriticalExtensions();
- }
-
- @Override public byte[] getExtensionValue(String oid) {
- if (extensions == null) {
- return null;
- }
- // retrieve the info from the cached extensions object
- Extension ext = extensions.getExtensionByOID(oid);
- return (ext == null) ? null : ext.getRawExtnValue();
- }
-
- @Override public boolean hasUnsupportedCriticalExtension() {
- if (extensions == null) {
- return false;
- }
- // retrieve the info from the cached extensions object
- return extensions.hasUnsupportedCritical();
- }
-
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java
deleted file mode 100644
index 3699700..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
-* @author Alexander Y. Kleymenov
-* @version $Revision$
-*/
-
-package org.apache.harmony.security.provider.cert;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.cert.CertPath;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.harmony.security.asn1.ASN1Any;
-import org.apache.harmony.security.asn1.ASN1Explicit;
-import org.apache.harmony.security.asn1.ASN1Implicit;
-import org.apache.harmony.security.asn1.ASN1Oid;
-import org.apache.harmony.security.asn1.ASN1Sequence;
-import org.apache.harmony.security.asn1.ASN1SequenceOf;
-import org.apache.harmony.security.asn1.ASN1Type;
-import org.apache.harmony.security.asn1.BerInputStream;
-import org.apache.harmony.security.pkcs7.ContentInfo;
-import org.apache.harmony.security.pkcs7.SignedData;
-import org.apache.harmony.security.x509.Certificate;
-
-/**
- * This class is an implementation of X.509 CertPath. This implementation
- * provides ability to create the instance of X.509 Certification Path
- * by several means:<br>
- *
- * &nbsp; 1. It can be created over the list of X.509 certificates
- * (implementations of X509Certificate class) provided in constructor.<br>
- *
- * &nbsp; 2. It can be created by means of <code>getInstance</code> methods
- * on the base of the following ASN.1 DER encoded forms:<br>
- *
- * &nbsp;&nbsp; - PkiPath as defined in
- * ITU-T Recommendation X.509(2000) Corrigendum 1(2001)
- * (can be seen at
- * ftp://ftp.bull.com/pub/OSIdirectory/DefectResolution/TechnicalCorrigenda/ApprovedTechnicalCorrigendaToX.509/8%7CX.509-TC1(4th).pdf)
- * <br>
- * &nbsp;&nbsp; - PKCS #7 SignedData object provided in the form of
- * ContentInfo structure. CertPath object is generated on the base of
- * certificates presented in <code>certificates</code> field of the SignedData
- * object which in its turn is retrieved from ContentInfo structure.
- * (see http://www.ietf.org/rfc/rfc2315.txt
- * for more info on PKCS #7)
- * <br>
- * &nbsp;
- */
-public class X509CertPathImpl extends CertPath {
- /**
- * @serial
- */
- private static final long serialVersionUID = 7989755106209515436L;
-
- /**
- * Supported encoding types for CerthPath. Used by the various APIs that
- * encode this into bytes such as {@link #getEncoded()}.
- */
- private enum Encoding {
- PKI_PATH("PkiPath"),
- PKCS7("PKCS7");
-
- private final String apiName;
-
- Encoding(String apiName) {
- this.apiName = apiName;
- }
-
- static Encoding findByApiName(String apiName) throws CertificateEncodingException {
- for (Encoding element : values()) {
- if (element.apiName.equals(apiName)) {
- return element;
- }
- }
-
- return null;
- }
- }
-
- /** Unmodifiable list of encodings for the API. */
- static final List<String> encodings = Collections.unmodifiableList(Arrays.asList(new String[] {
- Encoding.PKI_PATH.apiName,
- Encoding.PKCS7.apiName,
- }));
-
- /** The list of certificates in the order of target toward trust anchor. */
- private final List<X509Certificate> certificates;
-
- /** PkiPath encoding of the certification path. */
- private byte[] pkiPathEncoding;
-
- /** PKCS7 encoding of the certification path. */
- private byte[] pkcs7Encoding;
-
- /**
- * Creates an instance of X.509 CertPath over the specified list of
- * certificates.
- *
- * @throws CertificateException if some of the object in the list is not an
- * instance of subclass of X509Certificate.
- */
- public X509CertPathImpl(List<? extends java.security.cert.Certificate> certs)
- throws CertificateException {
- super("X.509");
-
- final int size = certs.size();
- certificates = new ArrayList<X509Certificate>(size);
-
- for (int i = 0; i < size; i++) {
- final java.security.cert.Certificate cert = certs.get(i);
- if (!(cert instanceof X509Certificate)) {
- throw new CertificateException("Certificate " + i + " is not an X.509 certificate");
- }
-
- certificates.add((X509Certificate) cert);
- }
- }
-
- /**
- * Creates an X.509 CertPath over the specified {@code certs}. The
- * {@code certs} should be sorted correctly when calling into the
- * constructor. Additionally, the {@code encodedPath} should match the
- * expected output for the {@code type} of encoding.
- */
- private X509CertPathImpl(List<X509Certificate> certs, Encoding type) {
- super("X.509");
-
- certificates = certs;
- }
-
- /**
- * Extract a CertPath from a PKCS#7 {@code contentInfo} object.
- */
- private static X509CertPathImpl getCertPathFromContentInfo(ContentInfo contentInfo)
- throws CertificateException {
- final SignedData sd = contentInfo.getSignedData();
- if (sd == null) {
- throw new CertificateException("Incorrect PKCS7 encoded form: missing signed data");
- }
-
- List<Certificate> certs = sd.getCertificates();
- if (certs == null) {
- certs = Collections.emptyList();
- }
-
- final List<X509Certificate> result = new ArrayList<X509Certificate>(certs.size());
- for (Certificate cert : certs) {
- result.add(new X509CertImpl(cert));
- }
-
- return new X509CertPathImpl(result, Encoding.PKCS7);
- }
-
- /**
- * Generates certification path object on the base of PkiPath encoded form
- * provided via input stream.
- *
- * @throws CertificateException if some problems occurred during the
- * decoding.
- */
- public static X509CertPathImpl getInstance(InputStream in) throws CertificateException {
- try {
- return (X509CertPathImpl) ASN1.decode(in);
- } catch (IOException e) {
- throw new CertificateException("Failed to decode CertPath", e);
- }
- }
-
- /**
- * Generates certification path object on the basis of encoding provided via
- * input stream. The format of provided encoded form is specified by
- * parameter <code>encoding</code>.
- *
- * @throws CertificateException if specified encoding form is not supported,
- * or some problems occurred during the decoding.
- */
- public static X509CertPathImpl getInstance(InputStream in, String encoding)
- throws CertificateException {
- try {
- final Encoding encType = Encoding.findByApiName(encoding);
- if (encType == null) {
- throw new CertificateException("Unsupported encoding: " + encoding);
- }
-
- switch (encType) {
- case PKI_PATH:
- return (X509CertPathImpl) ASN1.decode(in);
- case PKCS7:
- return getCertPathFromContentInfo((ContentInfo) ContentInfo.ASN1.decode(in));
- default:
- throw new CertificateException("Unsupported encoding: " + encoding);
- }
- } catch (IOException e) {
- throw new CertificateException("Failed to decode CertPath", e);
- }
- }
-
- /**
- * Generates certification path object on the base of PkiPath
- * encoded form provided via array of bytes.
- * @throws CertificateException if some problems occurred during
- * the decoding.
- */
- public static X509CertPathImpl getInstance(byte[] in) throws CertificateException {
- try {
- return (X509CertPathImpl) ASN1.decode(in);
- } catch (IOException e) {
- throw new CertificateException("Failed to decode CertPath", e);
- }
- }
-
- /**
- * Generates certification path object on the base of encoding provided via
- * array of bytes. The format of provided encoded form is specified by
- * parameter {@code encoding}.
- *
- * @throws CertificateException if specified encoding form is not supported,
- * or some problems occurred during the decoding.
- */
- public static X509CertPathImpl getInstance(byte[] in, String encoding)
- throws CertificateException {
- try {
- final Encoding encType = Encoding.findByApiName(encoding);
- if (encType == null) {
- throw new CertificateException("Unsupported encoding: " + encoding);
- }
-
- switch (encType) {
- case PKI_PATH:
- return (X509CertPathImpl) ASN1.decode(in);
- case PKCS7:
- return getCertPathFromContentInfo((ContentInfo) ContentInfo.ASN1.decode(in));
- default:
- throw new CertificateException("Unsupported encoding: " + encoding);
- }
- } catch (IOException e) {
- throw new CertificateException("Failed to decode CertPath", e);
- }
- }
-
- // ---------------------------------------------------------------------
- // ---- java.security.cert.CertPath abstract method implementations ----
- // ---------------------------------------------------------------------
-
- /**
- * @see java.security.cert.CertPath#getCertificates()
- * method documentation for more info
- */
- @Override
- public List<X509Certificate> getCertificates() {
- return Collections.unmodifiableList(certificates);
- }
-
- /**
- * Returns in PkiPath format which is our default encoding.
- *
- * @see java.security.cert.CertPath#getEncoded()
- */
- @Override
- public byte[] getEncoded() throws CertificateEncodingException {
- return getEncoded(Encoding.PKI_PATH);
- }
-
- /**
- * @see #getEncoded(String)
- */
- private byte[] getEncoded(Encoding encoding) throws CertificateEncodingException {
- switch (encoding) {
- case PKI_PATH:
- if (pkiPathEncoding == null) {
- pkiPathEncoding = ASN1.encode(this);
- }
-
- return pkiPathEncoding.clone();
- case PKCS7:
- if (pkcs7Encoding == null) {
- pkcs7Encoding = PKCS7_SIGNED_DATA_OBJECT.encode(this);
- }
-
- return pkcs7Encoding.clone();
- default:
- throw new CertificateEncodingException("Unsupported encoding: " + encoding);
- }
- }
-
- /**
- * @see java.security.cert.CertPath#getEncoded(String)
- */
- @Override
- public byte[] getEncoded(String encoding) throws CertificateEncodingException {
- final Encoding encType = Encoding.findByApiName(encoding);
- if (encType == null) {
- throw new CertificateEncodingException("Unsupported encoding: " + encoding);
- }
-
- return getEncoded(encType);
- }
-
- /**
- * @see java.security.cert.CertPath#getEncodings()
- * method documentation for more info
- */
- @Override
- public Iterator<String> getEncodings() {
- return encodings.iterator();
- }
-
- /**
- * ASN.1 DER Encoder/Decoder for PkiPath structure.
- */
- public static final ASN1SequenceOf ASN1 = new ASN1SequenceOf(ASN1Any.getInstance()) {
- /**
- * Builds the instance of X509CertPathImpl on the base of the list of
- * ASN.1 encodings of X.509 certificates provided via PkiPath structure.
- * This method participates in decoding process.
- */
- public Object getDecodedObject(BerInputStream in) throws IOException {
- // retrieve the decoded content
- final List<byte[]> encodedCerts = (List<byte[]>) in.content;
-
- final int size = encodedCerts.size();
- final List<X509Certificate> certificates = new ArrayList<X509Certificate>(size);
-
- for (int i = size - 1; i >= 0; i--) {
- // create the X.509 certificate on the base of its encoded form
- // and add it to the list.
- certificates.add(new X509CertImpl((Certificate) Certificate.ASN1
- .decode(encodedCerts.get(i))));
- }
-
- // create and return the resulting object
- return new X509CertPathImpl(certificates, Encoding.PKI_PATH);
- }
-
- /**
- * Returns the Collection of the encoded form of certificates contained
- * in the X509CertPathImpl object to be encoded.
- * This method participates in encoding process.
- */
- public Collection<byte[]> getValues(Object object) {
- // object to be encoded
- final X509CertPathImpl cp = (X509CertPathImpl) object;
-
- // if it has no certificates in it - create the sequence of size 0
- if (cp.certificates == null) {
- return Collections.emptyList();
- }
-
- final int size = cp.certificates.size();
- final List<byte[]> encodings = new ArrayList<byte[]>(size);
-
- try {
- for (int i = size - 1; i >= 0; i--) {
- // get the encoded form of certificate and place it into the
- // list to be encoded in PkiPath format
- encodings.add(cp.certificates.get(i).getEncoded());
- }
- } catch (CertificateEncodingException e) {
- throw new IllegalArgumentException("Encoding error occurred", e);
- }
-
- return encodings;
- }
- };
-
-
- /**
- * Encoder for PKCS#7 SignedData. It is assumed that only certificate field
- * is important all other fields contain pre-calculated encodings.
- */
- private static final ASN1Sequence ASN1_SIGNED_DATA = new ASN1Sequence(
- new ASN1Type[] {
- // version ,digestAlgorithms, content info
- ASN1Any.getInstance(),
- // certificates
- new ASN1Implicit(0, ASN1),
- // set of crls is optional and is missed here
- ASN1Any.getInstance(),// signers info
- }) {
-
- // precalculated ASN.1 encodings for
- // version ,digestAlgorithms, content info field of SignedData
- private final byte[] PRECALCULATED_HEAD = new byte[] { 0x02, 0x01,
- 0x01,// version (v1)
- 0x31, 0x00,// empty set of DigestAlgorithms
- 0x30, 0x03, 0x06, 0x01, 0x00 // empty ContentInfo with oid=0
- };
-
- // precalculated empty set of SignerInfos
- private final byte[] SIGNERS_INFO = new byte[] { 0x31, 0x00 };
-
- protected void getValues(Object object, Object[] values) {
- values[0] = PRECALCULATED_HEAD;
- values[1] = object; // pass X509CertPathImpl object
- values[2] = SIGNERS_INFO;
- }
-
- // stub to prevent using the instance as decoder
- public Object decode(BerInputStream in) throws IOException {
- throw new RuntimeException(
- "Invalid use of encoder for PKCS#7 SignedData object");
- }
- };
-
- private static final ASN1Sequence PKCS7_SIGNED_DATA_OBJECT = new ASN1Sequence(
- new ASN1Type[] { ASN1Any.getInstance(), // contentType
- new ASN1Explicit(0, ASN1_SIGNED_DATA) // SignedData
- }) {
-
- // precalculated ASN.1 encoding for SignedData object oid
- private final byte[] SIGNED_DATA_OID = ASN1Oid.getInstance().encode(
- ContentInfo.SIGNED_DATA);
-
- protected void getValues(Object object, Object[] values) {
- values[0] = SIGNED_DATA_OID;
- values[1] = object; // pass X509CertPathImpl object
- }
-
- // stub to prevent using the instance as decoder
- public Object decode(BerInputStream in) throws IOException {
- throw new RuntimeException(
- "Invalid use of encoder for PKCS#7 SignedData object");
- }
- };
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/CryptoProvider.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/CryptoProvider.java
index 7c2785a..ad5ac7d 100644
--- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/CryptoProvider.java
+++ b/luni/src/main/java/org/apache/harmony/security/provider/crypto/CryptoProvider.java
@@ -20,12 +20,9 @@ package org.apache.harmony.security.provider.crypto;
import java.security.Provider;
/**
- * Implementation of Provider for SecureRandom, MessageDigest and Signature
- * using a Secure Hash Algorithm, SHA-1;
- * see SECURE HASH STANDARD, FIPS PUB 180-1 (http://www.itl.nist.gov/fipspubs/fip180-1.htm) <BR>
- * <BR>
- * The implementation supports "SHA1PRNG", "SHA-1" and "SHA1withDSA" algorithms described in
- * JavaTM Cryptography Architecture, API Specification & Reference
+ * Implementation of Provider for SecureRandom. The implementation supports the
+ * "SHA1PRNG" algorithm described in JavaTM Cryptography Architecture, API
+ * Specification & Reference
*/
public final class CryptoProvider extends Provider {
@@ -36,46 +33,10 @@ public final class CryptoProvider extends Provider {
* Creates a Provider and puts parameters
*/
public CryptoProvider() {
-
super("Crypto", 1.0, "HARMONY (SHA1 digest; SecureRandom; SHA1withDSA signature)");
- // names of classes implementing services
- final String MD_NAME = "org.apache.harmony.security.provider.crypto.SHA1_MessageDigestImpl";
- final String SR_NAME = "org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl";
-
- final String SIGN_NAME = "org.apache.harmony.security.provider.crypto.SHA1withDSA_SignatureImpl";
-
- final String SIGN_ALIAS = "SHA1withDSA";
-
-
- final String KEYF_NAME = "org.apache.harmony.security.provider.crypto.DSAKeyFactoryImpl";
-
- put("MessageDigest.SHA-1", MD_NAME);
- put("MessageDigest.SHA-1 ImplementedIn", "Software");
- put("Alg.Alias.MessageDigest.SHA1", "SHA-1");
- put("Alg.Alias.MessageDigest.SHA", "SHA-1");
-
- put("SecureRandom.SHA1PRNG", SR_NAME);
+ put("SecureRandom.SHA1PRNG",
+ "org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl");
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
-
- put("Signature.SHA1withDSA", SIGN_NAME);
- put("Signature.SHA1withDSA ImplementedIn", "Software");
- put("Alg.Alias.Signature.SHAwithDSA", SIGN_ALIAS);
- put("Alg.Alias.Signature.DSAwithSHA1", SIGN_ALIAS);
- put("Alg.Alias.Signature.SHA1/DSA", SIGN_ALIAS);
- put("Alg.Alias.Signature.SHA/DSA", SIGN_ALIAS);
- put("Alg.Alias.Signature.SHA-1/DSA", SIGN_ALIAS);
- put("Alg.Alias.Signature.DSA", SIGN_ALIAS);
- put("Alg.Alias.Signature.DSS", SIGN_ALIAS);
-
- put("Alg.Alias.Signature.OID.1.2.840.10040.4.3", SIGN_ALIAS);
- put("Alg.Alias.Signature.1.2.840.10040.4.3", SIGN_ALIAS);
- put("Alg.Alias.Signature.1.3.14.3.2.13", SIGN_ALIAS);
- put("Alg.Alias.Signature.1.3.14.3.2.27", SIGN_ALIAS);
-
- put("KeyFactory.DSA", KEYF_NAME);
- put("KeyFactory.DSA ImplementedIn", "Software");
- put("Alg.Alias.KeyFactory.1.3.14.3.2.12", "DSA");
- put("Alg.Alias.KeyFactory.1.2.840.10040.4.1", "DSA");
}
}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAKeyFactoryImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAKeyFactoryImpl.java
deleted file mode 100644
index 690d16e..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAKeyFactoryImpl.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.harmony.security.provider.crypto;
-
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyFactorySpi;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.DSAParams;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.DSAPublicKey;
-import java.security.spec.DSAPrivateKeySpec;
-import java.security.spec.DSAPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-
-public class DSAKeyFactoryImpl extends KeyFactorySpi {
-
- /**
- * This method generates a DSAPrivateKey object from the provided key specification.
- *
- * @param
- * keySpec - the specification (key material) for the DSAPrivateKey.
- *
- * @return
- * a DSAPrivateKey object
- *
- * @throws InvalidKeySpecException
- * if "keySpec" is neither DSAPrivateKeySpec nor PKCS8EncodedKeySpec
- */
- protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
- throws InvalidKeySpecException {
-
- if (keySpec != null) {
- if (keySpec instanceof DSAPrivateKeySpec) {
-
- return new DSAPrivateKeyImpl((DSAPrivateKeySpec) keySpec);
- }
- if (keySpec instanceof PKCS8EncodedKeySpec) {
-
- return new DSAPrivateKeyImpl((PKCS8EncodedKeySpec) keySpec);
- }
- }
- throw new InvalidKeySpecException("'keySpec' is neither DSAPrivateKeySpec nor PKCS8EncodedKeySpec");
- }
-
- /**
- * This method generates a DSAPublicKey object from the provided key specification.
- *
- * @param
- * keySpec - the specification (key material) for the DSAPublicKey.
- *
- * @return
- * a DSAPublicKey object
- *
- * @throws InvalidKeySpecException
- * if "keySpec" is neither DSAPublicKeySpec nor X509EncodedKeySpec
- */
- protected PublicKey engineGeneratePublic(KeySpec keySpec)
- throws InvalidKeySpecException {
-
- if (keySpec != null) {
- if (keySpec instanceof DSAPublicKeySpec) {
-
- return new DSAPublicKeyImpl((DSAPublicKeySpec) keySpec);
- }
- if (keySpec instanceof X509EncodedKeySpec) {
-
- return new DSAPublicKeyImpl((X509EncodedKeySpec) keySpec);
- }
- }
- throw new InvalidKeySpecException("'keySpec' is neither DSAPublicKeySpec nor X509EncodedKeySpec");
- }
-
- /**
- * This method returns a specification for the supplied key.
- *
- * The specification will be returned in the form of an object of the type
- * specified by keySpec.
- *
- * @param key -
- * either DSAPrivateKey or DSAPublicKey
- * @param keySpec -
- * either DSAPrivateKeySpec.class or DSAPublicKeySpec.class
- *
- * @return either a DSAPrivateKeySpec or a DSAPublicKeySpec
- *
- * @throws InvalidKeySpecException
- * if "keySpec" is not a specification for DSAPublicKey or
- * DSAPrivateKey
- */
- protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
- throws InvalidKeySpecException {
-
- BigInteger p, q, g, x, y;
-
- if (key != null) {
- if (keySpec == null) {
- throw new NullPointerException("keySpec == null");
- }
- if (key instanceof DSAPrivateKey) {
- DSAPrivateKey privateKey = (DSAPrivateKey) key;
-
- if (keySpec.equals(DSAPrivateKeySpec.class)) {
-
- x = privateKey.getX();
-
- DSAParams params = privateKey.getParams();
-
- p = params.getP();
- q = params.getQ();
- g = params.getG();
-
- return (T) (new DSAPrivateKeySpec(x, p, q, g));
- }
-
- if (keySpec.equals(PKCS8EncodedKeySpec.class)) {
- return (T) (new PKCS8EncodedKeySpec(key.getEncoded()));
- }
-
- throw new InvalidKeySpecException("'keySpec' is neither DSAPrivateKeySpec nor PKCS8EncodedKeySpec");
- }
-
- if (key instanceof DSAPublicKey) {
- DSAPublicKey publicKey = (DSAPublicKey) key;
-
- if (keySpec.equals(DSAPublicKeySpec.class)) {
-
- y = publicKey.getY();
-
- DSAParams params = publicKey.getParams();
-
- p = params.getP();
- q = params.getQ();
- g = params.getG();
-
- return (T) (new DSAPublicKeySpec(y, p, q, g));
- }
-
- if (keySpec.equals(X509EncodedKeySpec.class)) {
- return (T) (new X509EncodedKeySpec(key.getEncoded()));
- }
-
- throw new InvalidKeySpecException("'keySpec' is neither DSAPublicKeySpec nor X509EncodedKeySpec");
- }
- }
- throw new InvalidKeySpecException("'key' is neither DSAPublicKey nor DSAPrivateKey");
- }
-
- /**
- * The method generates a DSAPublicKey object from the provided key.
- *
- * @param
- * key - a DSAPublicKey object or DSAPrivateKey object.
- *
- * @return
- * object of the same type as the "key" argument
- *
- * @throws InvalidKeyException
- * if "key" is neither DSAPublicKey nor DSAPrivateKey
- */
- protected Key engineTranslateKey(Key key) throws InvalidKeyException {
-
- if (key != null) {
- if (key instanceof DSAPrivateKey) {
-
- DSAPrivateKey privateKey = (DSAPrivateKey) key;
- DSAParams params = privateKey.getParams();
-
- try {
- return engineGeneratePrivate(new DSAPrivateKeySpec(
- privateKey.getX(), params.getP(), params.getQ(),
- params.getG()));
- } catch (InvalidKeySpecException e) {
- // Actually this exception shouldn't be thrown
- throw new InvalidKeyException("ATTENTION: InvalidKeySpecException: " + e);
- }
- }
-
- if (key instanceof DSAPublicKey) {
-
- DSAPublicKey publicKey = (DSAPublicKey) key;
- DSAParams params = publicKey.getParams();
-
- try {
- return engineGeneratePublic(new DSAPublicKeySpec(publicKey
- .getY(), params.getP(), params.getQ(), params
- .getG()));
- } catch (InvalidKeySpecException e) {
- // Actually this exception shouldn't be thrown
- throw new InvalidKeyException("ATTENTION: InvalidKeySpecException: " + e);
- }
- }
- }
- throw new InvalidKeyException("'key' is neither DSAPublicKey nor DSAPrivateKey");
- }
-
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPrivateKeyImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPrivateKeyImpl.java
deleted file mode 100644
index c0fc766..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPrivateKeyImpl.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- /*
- * TODO
- * 1. The class extends the PrivateKeyImpl class in "org.apache.harmony.security" package.
- *
- * 2. See a compatibility with RI comments
- * in the below "DSAPrivateKeyImpl(PKCS8EncodedKeySpec keySpec)" constructor.
- */
-
-
-package org.apache.harmony.security.provider.crypto;
-
-import java.io.IOException;
-import java.io.NotActiveException;
-import java.math.BigInteger;
-import java.security.interfaces.DSAParams;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.spec.DSAParameterSpec;
-import java.security.spec.DSAPrivateKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import org.apache.harmony.security.PrivateKeyImpl;
-import org.apache.harmony.security.asn1.ASN1Integer;
-import org.apache.harmony.security.pkcs8.PrivateKeyInfo;
-import org.apache.harmony.security.utils.AlgNameMapper;
-import org.apache.harmony.security.x509.AlgorithmIdentifier;
-
-/**
- * The class provides DSAPrivateKey functionality by extending a class implementing PrivateKey
- * and implementing methods defined in both interfaces, DSAKey and DSAPrivateKey
- */
-public class DSAPrivateKeyImpl extends PrivateKeyImpl implements DSAPrivateKey {
-
- /**
- * @serial
- */
- private static final long serialVersionUID = -4716227614104950081L;
-
- private BigInteger x, g, p, q;
-
- private transient DSAParams params;
-
- /**
- * Creates object from DSAPrivateKeySpec.
- *
- * @param keySpec - a DSAPrivateKeySpec object
- */
- public DSAPrivateKeyImpl(DSAPrivateKeySpec keySpec) {
-
- super("DSA");
-
- PrivateKeyInfo pki;
-
- g = keySpec.getG();
- p = keySpec.getP();
- q = keySpec.getQ();
-
- ThreeIntegerSequence threeInts = new ThreeIntegerSequence(p
- .toByteArray(), q.toByteArray(), g.toByteArray());
-
- AlgorithmIdentifier ai = new AlgorithmIdentifier(AlgNameMapper
- .map2OID("DSA"),
- threeInts.getEncoded());
- x = keySpec.getX();
-
- pki = new PrivateKeyInfo(0, ai, ASN1Integer.getInstance().encode(
- x.toByteArray()), null);
-
- setEncoding(pki.getEncoded());
-
- params = new DSAParameterSpec(p, q, g);
- }
-
- /**
- * Creates object from PKCS8EncodedKeySpec.
- *
- * @param keySpec - a XPKCS8EncodedKeySpec object
- *
- * @throws InvalidKeySpecException - if key data cannot be obtain from encoded format
- */
- public DSAPrivateKeyImpl(PKCS8EncodedKeySpec keySpec)
- throws InvalidKeySpecException {
-
- super("DSA");
-
- AlgorithmIdentifier ai;
- ThreeIntegerSequence threeInts = null;
-
- String alg, algName;
-
- byte[] encoding = keySpec.getEncoded();
-
- PrivateKeyInfo privateKeyInfo = null;
-
- try {
- privateKeyInfo = (PrivateKeyInfo) PrivateKeyInfo.ASN1
- .decode(encoding);
- } catch (IOException e) {
- throw new InvalidKeySpecException("Failed to decode keySpec encoding: " + e);
- }
-
- try {
- x = new BigInteger((byte[]) ASN1Integer.getInstance().decode(
- privateKeyInfo.getPrivateKey()));
- } catch (IOException e) {
- throw new InvalidKeySpecException("Failed to decode parameters: " + e);
- }
-
- ai = privateKeyInfo.getAlgorithmIdentifier();
- try {
- threeInts = (ThreeIntegerSequence) ThreeIntegerSequence.ASN1
- .decode(ai.getParameters());
- } catch (IOException e) {
- throw new InvalidKeySpecException("Failed to decode parameters: " + e);
- }
- p = new BigInteger(threeInts.p);
- q = new BigInteger(threeInts.q);
- g = new BigInteger(threeInts.g);
- params = new DSAParameterSpec(p, q, g);
- setEncoding(encoding);
-
- /*
- * the following code implements RI behavior
- */
- alg = ai.getAlgorithm();
- algName = AlgNameMapper.map2AlgName(alg);
- setAlgorithm(algName == null ? alg : algName);
- }
-
- public BigInteger getX() {
- return x;
- }
-
- public DSAParams getParams() {
- return params;
- }
-
- private void readObject(java.io.ObjectInputStream in) throws NotActiveException, IOException, ClassNotFoundException {
- in.defaultReadObject();
- params = new DSAParameterSpec(p, q, g);
- }
-
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPublicKeyImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPublicKeyImpl.java
deleted file mode 100644
index 6b35970..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPublicKeyImpl.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- /*
- * TODO
- * 1. The class extends the PublicKeyImpl class in "org.apache.harmony.security" package.
- *
- * 2. The class uses methods in the auxiliary non-public "ThreeIntegerSequence" class
- * defined along with the "DSAPrivateKeyImpl" class.
- *
- * 3. See a compatibility with RI comments
- * in the below "DSAPublicKeyImpl(X509EncodedKeySpec keySpec)" constructor.
- */
-
-package org.apache.harmony.security.provider.crypto;
-
-import java.io.IOException;
-import java.io.NotActiveException;
-import java.math.BigInteger;
-import java.security.interfaces.DSAParams;
-import java.security.interfaces.DSAPublicKey;
-import java.security.spec.DSAParameterSpec;
-import java.security.spec.DSAPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
-import org.apache.harmony.security.PublicKeyImpl;
-import org.apache.harmony.security.asn1.ASN1Integer;
-import org.apache.harmony.security.utils.AlgNameMapper;
-import org.apache.harmony.security.x509.AlgorithmIdentifier;
-import org.apache.harmony.security.x509.SubjectPublicKeyInfo;
-
-/**
- * The class provides DSAPublicKey functionality by extending a class implementing PublicKey
- * and implementing methods defined in both interfaces, DSAKey and DSAPublicKey
- */
-public class DSAPublicKeyImpl extends PublicKeyImpl implements DSAPublicKey {
-
- /**
- * @serial
- */
- private static final long serialVersionUID = -2279672131310978336L;
-
- private BigInteger y, g, p, q;
-
- private transient DSAParams params;
-
- /**
- * Creates object from DSAPublicKeySpec.
- *
- * @param keySpec - a DSAPublicKeySpec object
- */
- public DSAPublicKeyImpl(DSAPublicKeySpec keySpec) {
-
- super("DSA");
-
- SubjectPublicKeyInfo spki;
-
- p = keySpec.getP();
- q = keySpec.getQ();
- g = keySpec.getG();
-
- ThreeIntegerSequence threeInts = new ThreeIntegerSequence(p
- .toByteArray(), q.toByteArray(), g.toByteArray());
-
- AlgorithmIdentifier ai = new AlgorithmIdentifier(AlgNameMapper
- .map2OID("DSA"),
- threeInts.getEncoded());
-
- y = keySpec.getY();
-
- spki = new SubjectPublicKeyInfo(ai, ASN1Integer.getInstance().encode(
- y.toByteArray()));
- setEncoding(spki.getEncoded());
-
- params = (DSAParams) (new DSAParameterSpec(p, q, g));
- }
-
- /**
- * Creates object from X509EncodedKeySpec.
- *
- * @param keySpec - a X509EncodedKeySpec object
- *
- * @throws InvalidKeySpecException - if key data cannot be obtain from encoded format
- */
- public DSAPublicKeyImpl(X509EncodedKeySpec keySpec)
- throws InvalidKeySpecException {
-
- super("DSA");
-
- AlgorithmIdentifier ai;
- ThreeIntegerSequence threeInts = null;
-
- SubjectPublicKeyInfo subjectPublicKeyInfo = null;
-
- byte[] encoding = keySpec.getEncoded();
-
- String alg, algName;
-
- try {
- subjectPublicKeyInfo = (SubjectPublicKeyInfo) SubjectPublicKeyInfo.ASN1
- .decode(encoding);
- } catch (IOException e) {
- throw new InvalidKeySpecException("Failed to decode keySpec encoding: " + e);
- }
-
- try {
- y = new BigInteger((byte[]) ASN1Integer.getInstance().decode(
- subjectPublicKeyInfo.getSubjectPublicKey()));
- } catch (IOException e) {
- throw new InvalidKeySpecException("Failed to decode parameters: " + e);
- }
-
- ai = subjectPublicKeyInfo.getAlgorithmIdentifier();
-
- try {
- threeInts = (ThreeIntegerSequence) ThreeIntegerSequence.ASN1
- .decode(ai.getParameters());
- } catch (IOException e) {
- throw new InvalidKeySpecException("Failed to decode parameters: " + e);
- }
- p = new BigInteger(threeInts.p);
- q = new BigInteger(threeInts.q);
- g = new BigInteger(threeInts.g);
- params = (DSAParams) (new DSAParameterSpec(p, q, g));
-
- setEncoding(encoding);
-
- /*
- * the following code implements RI behavior
- */
- alg = ai.getAlgorithm();
- algName = AlgNameMapper.map2AlgName(alg);
- setAlgorithm(algName == null ? alg : algName);
- }
-
- /**
- * @return
- * a value of a public key (y).
- */
- public BigInteger getY() {
- return y;
- }
-
- /**
- * @return
- * DSA key parameters (p, q, g).
- */
- public DSAParams getParams() {
- return params;
- }
-
- private void readObject(java.io.ObjectInputStream in) throws NotActiveException, IOException, ClassNotFoundException {
- in.defaultReadObject();
- params = new DSAParameterSpec(p, q, g);
- }
-
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1_MessageDigestImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1_MessageDigestImpl.java
deleted file mode 100644
index 3f41f18..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1_MessageDigestImpl.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package org.apache.harmony.security.provider.crypto;
-
-import java.security.DigestException;
-import java.security.MessageDigestSpi;
-import java.util.Arrays;
-
-import static org.apache.harmony.security.provider.crypto.SHA1Constants.*;
-
-/**
- * This class extends the MessageDigestSpi class implementing all its abstract methods;
- * it overrides the "Object clone()" and "int engineGetDigestLength()" methods. <BR>
- * The class implements the Cloneable interface.
- */
-public class SHA1_MessageDigestImpl extends MessageDigestSpi implements Cloneable {
- private int[] buffer; // buffer has the following structure:
- // - 0-16 - frame for accumulating a message
- // - 17-79 - for SHA1Impl methods
- // - 80 - unused
- // - 81 - to store length of the message
- // - 82-86 - frame for current message digest
-
- private byte[] oneByte; // one byte buffer needed to use in engineUpdate(byte)
- // having buffer as private field is just optimization
-
- private long messageLength; // total length of bytes supplied by user
-
-
- /**
- * The constructor creates needed buffers and sets the engine at initial state
- */
- public SHA1_MessageDigestImpl() {
-
- // BYTES_OFFSET +6 is minimal length required by methods in SHA1Impl
- buffer = new int[BYTES_OFFSET +6];
-
- oneByte = new byte[1];
-
- engineReset();
- }
-
-
- /**
- * The method performs final actions and invokes the "computeHash(int[])" method.
- * In case if there is no enough words in current frame
- * after processing its data, extra frame is prepared and
- * the "computeHash(int[])" method is invoked second time. <BR>
- *
- * After processing, the method resets engine's state
- *
- * @param
- * digest - byte array
- * @param
- * offset - offset in digest
- */
- private void processDigest(byte[] digest, int offset) {
-
- int i, j; // implementation variables
- int lastWord; //
-
- long nBits = messageLength <<3 ; // length has to be calculated before padding
-
- engineUpdate( (byte) 0x80 ); // beginning byte in padding
-
- i = 0; // i contains number of beginning word for following loop
-
- lastWord = (buffer[BYTES_OFFSET] + 3)>>2 ; // computing of # of full words by shifting
- // # of bytes
-
- // possible cases:
- //
- // - buffer[BYTES_OFFSET] == 0 - buffer frame is empty,
- // padding byte was 64th in previous frame
- // current frame should contain only message's length
- //
- // - lastWord < 14 - two last, these are 14 & 15, words in 16 word frame are free;
- // no extra frame needed
- // - lastWord = 14 - only one last, namely 15-th, word in frame doesn't contain bytes;
- // extra frame is needed
- // - lastWord > 14 - last word in frame is not full;
- // extra frame is needed
-
- if ( buffer[BYTES_OFFSET] != 0 ) {
-
- if ( lastWord < 15 ) {
- i = lastWord;
- } else {
- if ( lastWord == 15 ) {
- buffer[15] = 0; // last word in frame is set to "0"
- }
- SHA1Impl.computeHash(buffer);
- i = 0;
- }
- }
- Arrays.fill(buffer, i, 14, 0);
-
- buffer[14] = (int)( nBits >>>32 );
- buffer[15] = (int)( nBits & 0xFFFFFFFF );
- SHA1Impl.computeHash(buffer);
-
- // converting 5-word frame into 20 bytes
- j = offset;
- for ( i = HASH_OFFSET; i < HASH_OFFSET +5; i++ ) {
- int k = buffer[i];
- digest[j ] = (byte) ( k >>>24 ); // getting first byte from left
- digest[j+1] = (byte) ( k >>>16 ); // getting second byte from left
- digest[j+2] = (byte) ( k >>> 8 ); // getting third byte from left
- digest[j+3] = (byte) ( k ); // getting fourth byte from left
- j += 4;
- }
-
- engineReset();
- }
-
- // methods specified in java.security.MessageDigestSpi
-
- /**
- * Returns a "deep" copy of this SHA1MDImpl object. <BR>
- *
- * The method overrides "clone()" in class Object. <BR>
- *
- * @return
- * a clone of this object
- */
- public Object clone() throws CloneNotSupportedException {
- SHA1_MessageDigestImpl cloneObj = (SHA1_MessageDigestImpl) super.clone();
- cloneObj.buffer = buffer.clone();
- cloneObj.oneByte = oneByte.clone();
- return cloneObj;
- }
-
-
- /**
- * Computes a message digest value. <BR>
- *
- * The method resets the engine. <BR>
- *
- * The method overrides "engineDigest()" in class MessageDigestSpi. <BR>
- *
- * @return
- * byte array containing message digest value
- */
- protected byte[] engineDigest() {
- byte[] hash = new byte[DIGEST_LENGTH];
- processDigest(hash, 0);
- return hash;
- }
-
-
- /**
- * Computes message digest value.
- * Upon return, the value is stored in "buf" buffer beginning "offset" byte. <BR>
- *
- * The method resets the engine. <BR>
- *
- * The method overrides "engineDigest(byte[],int,int) in class MessageDigestSpi.
- *
- * @param
- * buf byte array to store a message digest returned
- * @param
- * offset a position in the array for first byte of the message digest
- * @param
- * len number of bytes within buffer allotted for the message digest;
- * as this implementation doesn't provide partial digests,
- * len should be >= 20, DigestException is thrown otherwise
- * @return
- * the length of the message digest stored in the "buf" buffer;
- * in this implementation the length=20
- *
- * @throws IllegalArgumentException
- * if null is passed to the "buf" argument <BR>
- * if offset + len > buf.length <BR>
- * if offset > buf.length or len > buf.length
- *
- * @throws DigestException
- * if len < 20
- *
- * @throws ArrayIndexOutOfBoundsException
- * if offset < 0
- */
- protected int engineDigest(byte[] buf, int offset, int len) throws DigestException {
- if (buf == null) {
- throw new IllegalArgumentException("buf == null");
- }
- if (offset > buf.length || len > buf.length || (len + offset) > buf.length) {
- throw new IllegalArgumentException();
- }
- if (len < DIGEST_LENGTH) {
- throw new DigestException("len < DIGEST_LENGTH");
- }
- if (offset < 0) {
- throw new ArrayIndexOutOfBoundsException(offset);
- }
-
- processDigest(buf, offset);
-
- return DIGEST_LENGTH;
- }
-
-
- /**
- * Returns a message digest length. <BR>
- *
- * The method overrides "engineGetDigestLength()" in class MessageDigestSpi. <BR>
- *
- * @return
- * total length of current message digest as an int value
- */
- protected int engineGetDigestLength() {
- return DIGEST_LENGTH;
- }
-
-
- /**
- * Resets the engine. <BR>
- *
- * The method overrides "engineReset()" in class MessageDigestSpi. <BR>
- */
- protected void engineReset() {
-
- messageLength = 0;
-
- buffer[BYTES_OFFSET] = 0;
- buffer[HASH_OFFSET ] = H0;
- buffer[HASH_OFFSET +1] = H1;
- buffer[HASH_OFFSET +2] = H2;
- buffer[HASH_OFFSET +3] = H3;
- buffer[HASH_OFFSET +4] = H4;
- }
-
-
- /**
- * Supplements a byte to current message. <BR>
- *
- * The method overrides "engineUpdate(byte)" in class MessageDigestSpi. <BR>
- *
- * @param
- * input byte to add to current message
- */
- protected void engineUpdate(byte input) {
-
- oneByte[0] = input;
- SHA1Impl.updateHash( buffer, oneByte, 0, 0 );
- messageLength++;
- }
-
-
- /**
- * Updates current message. <BR>
- *
- * The method overrides "engineUpdate(byte[],int,int)" in class MessageDigestSpi. <BR>
- *
- * The method silently returns if "len" <= 0.
- *
- * @param
- * input a byte array
- * @param
- * offset a number of first byte in the "input" array to use for updating
- * @param
- * len a number of bytes to use
- *
- * @throws NullPointerException
- * if null is passed to the "buf" argument
- *
- * @throws IllegalArgumentException
- * if offset > buf.length or len > buf.length or
- * (len + offset) > buf.length
- * @throws ArrayIndexOutOfBoundsException
- * offset < 0
- */
- protected void engineUpdate(byte[] input, int offset, int len) {
- if (input == null) {
- throw new IllegalArgumentException("input == null");
- }
- if (len <= 0) {
- return;
- }
- if (offset < 0) {
- throw new ArrayIndexOutOfBoundsException(offset);
- }
- if (offset > input.length || len > input.length || (len + offset) > input.length) {
- throw new IllegalArgumentException();
- }
-
- SHA1Impl.updateHash(buffer, input, offset, offset + len -1 );
- messageLength += len;
- }
-
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1withDSA_SignatureImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1withDSA_SignatureImpl.java
deleted file mode 100644
index 2958e00..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1withDSA_SignatureImpl.java
+++ /dev/null
@@ -1,423 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.harmony.security.provider.crypto;
-
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.InvalidParameterException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.interfaces.DSAKey;
-import java.security.interfaces.DSAParams;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.DSAPublicKey;
-
-public class SHA1withDSA_SignatureImpl extends Signature {
-
- private MessageDigest msgDigest;
-
- private DSAKey dsaKey;
-
- /**
- * The solo constructor.
- */
- public SHA1withDSA_SignatureImpl() throws NoSuchAlgorithmException {
-
- super("SHA1withDSA");
-
- msgDigest = MessageDigest.getInstance("SHA1");
- }
-
- /**
- * Deprecated method.
- *
- * @return
- * null
- */
- protected Object engineGetParameter(String param)
- throws InvalidParameterException {
- if (param == null) {
- throw new NullPointerException("param == null");
- }
- return null;
- }
-
- /**
- * Initializes this signature object with PrivateKey object
- * passed as argument to the method.
- *
- * @params
- * privateKey DSAPrivateKey object
- * @throws
- * InvalidKeyException if privateKey is not DSAPrivateKey object
- */
- protected void engineInitSign(PrivateKey privateKey)
- throws InvalidKeyException {
-
- DSAParams params;
-
- // parameters and private key
- BigInteger p, q, x;
-
- int n;
-
- if (privateKey == null || !(privateKey instanceof DSAPrivateKey)) {
- throw new InvalidKeyException();
- }
-
- params = ((DSAPrivateKey) privateKey).getParams();
- p = params.getP();
- q = params.getQ();
- x = ((DSAPrivateKey) privateKey).getX();
-
- // checks described in DSA standard
- n = p.bitLength();
- if (p.compareTo(BigInteger.valueOf(1)) != 1 || n < 512 || n > 1024 || (n & 077) != 0) {
- throw new InvalidKeyException("bad p");
- }
- if (q.signum() != 1 && q.bitLength() != 160) {
- throw new InvalidKeyException("bad q");
- }
- if (x.signum() != 1 || x.compareTo(q) != -1) {
- throw new InvalidKeyException("x <= 0 || x >= q");
- }
-
- dsaKey = (DSAKey) privateKey;
-
- msgDigest.reset();
- }
-
- /**
- * Initializes this signature object with PublicKey object
- * passed as argument to the method.
- *
- * @params
- * publicKey DSAPublicKey object
- * @throws
- * InvalidKeyException if publicKey is not DSAPublicKey object
- */
- protected void engineInitVerify(PublicKey publicKey)
- throws InvalidKeyException {
-
- // parameters and public key
- BigInteger p, q, y;
-
- int n1;
-
- if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
- throw new InvalidKeyException("publicKey is not an instance of DSAPublicKey");
- }
-
- DSAParams params = ((DSAPublicKey) publicKey).getParams();
- p = params.getP();
- q = params.getQ();
- y = ((DSAPublicKey) publicKey).getY();
-
- // checks described in DSA standard
- n1 = p.bitLength();
- if (p.compareTo(BigInteger.valueOf(1)) != 1 || n1 < 512 || n1 > 1024 || (n1 & 077) != 0) {
- throw new InvalidKeyException("bad p");
- }
- if (q.signum() != 1 || q.bitLength() != 160) {
- throw new InvalidKeyException("bad q");
- }
- if (y.signum() != 1) {
- throw new InvalidKeyException("y <= 0");
- }
-
- dsaKey = (DSAKey) publicKey;
-
- msgDigest.reset();
- }
-
- /*
- * Deprecated method.
- *
- * @throws
- * InvalidParameterException
- */
- protected void engineSetParameter(String param, Object value) throws InvalidParameterException {
- if (param == null) {
- throw new NullPointerException("param == null");
- }
- throw new InvalidParameterException("invalid parameter for this engine");
- }
-
- /**
- * Returns signature bytes as byte array containing
- * ASN1 representation for two BigInteger objects
- * which is SEQUENCE of two INTEGERS.
- * Length of sequence varies from less than 46 to 48.
- *
- * Resets object to the state it was in
- * when previous call to either "initSign" method was called.
- *
- * @return
- * byte array containing signature in ASN1 representation
- * @throws
- * SignatureException if object's state is not SIGN or
- * signature algorithm cannot process data
- */
-
- protected byte[] engineSign() throws SignatureException {
-
- // names of below BigIntegers are the same as they are defined in DSA standard
- BigInteger r = null;
- BigInteger s = null;
- BigInteger k = null;
-
- // parameters and private key
- BigInteger p, q, g, x;
-
- // BigInteger for message digest
- BigInteger digestBI;
-
- // various byte array being used in computing signature
- byte[] randomBytes;
- byte[] rBytes;
- byte[] sBytes;
- byte[] signature;
-
- int n, n1, n2;
-
- DSAParams params;
-
- if (appRandom == null) {
- appRandom = new SecureRandom();
- }
-
- params = dsaKey.getParams();
- p = params.getP();
- q = params.getQ();
- g = params.getG();
- x = ((DSAPrivateKey) dsaKey).getX();
-
- // forming signature according algorithm described in chapter 5 of DSA standard
-
- digestBI = new BigInteger(1, msgDigest.digest());
-
- randomBytes = new byte[20];
-
- for (;;) {
-
- appRandom.nextBytes(randomBytes);
-
- k = new BigInteger(1, randomBytes);
- if (k.compareTo(q) != -1) {
- continue;
- }
- r = g.modPow(k, p).mod(q);
- if (r.signum() == 0) {
- continue;
- }
-
- s = k.modInverse(q).multiply(digestBI.add(x.multiply(r)).mod(q))
- .mod(q);
-
- if (s.signum() != 0) {
- break;
- }
- }
-
- // forming signature's ASN1 representation which is SEQUENCE of two INTEGERs
- //
- rBytes = r.toByteArray();
- n1 = rBytes.length;
- if ((rBytes[0] & 0x80) != 0) {
- n1++;
- }
- sBytes = s.toByteArray();
- n2 = sBytes.length;
- if ((sBytes[0] & 0x80) != 0) {
- n2++;
- }
-
- signature = new byte[6 + n1 + n2]; // 48 is max. possible length of signature
- signature[0] = (byte) 0x30; // ASN1 SEQUENCE tag
- signature[1] = (byte) (4 + n1 + n2); // total length of two INTEGERs
- signature[2] = (byte) 0x02; // ASN1 INTEGER tag
- signature[3] = (byte) n1; // length of r
- signature[4 + n1] = (byte) 0x02; // ASN1 INTEGER tag
- signature[5 + n1] = (byte) n2; // length of s
-
- if (n1 == rBytes.length) {
- n = 4;
- } else {
- n = 5;
- }
- System.arraycopy(rBytes, 0, signature, n, rBytes.length);
-
- if (n2 == sBytes.length) {
- n = 6 + n1;
- } else {
- n = 7 + n1;
- }
- System.arraycopy(sBytes, 0, signature, n, sBytes.length);
-
- return signature;
- }
-
- /**
- * Updates data to sign or to verify.
- *
- * @params
- * b byte to update
- * @throws
- * SignatureException if object was not initialized for signing or verifying
- */
- protected void engineUpdate(byte b) throws SignatureException {
-
- msgDigest.update(b);
- }
-
- /**
- * Updates data to sign or to verify.
- *
- * @params
- * b byte array containing bytes to update
- * @params
- * off offset in byte array to start from
- * @params
- * len number of bytes to use for updating
- * @throws
- * SignatureException if object was not initialized for signing or verifying
- */
- protected void engineUpdate(byte[] b, int off, int len)
- throws SignatureException {
-
- msgDigest.update(b, off, len);
- }
-
- private boolean checkSignature(byte[] sigBytes, int offset, int length)
- throws SignatureException {
-
- // names of below BigIntegers are the same as they are defined in DSA standard
- BigInteger r, s, w;
- BigInteger u1, u2, v;
-
- // parameters and public key
- BigInteger p, q, g, y;
-
- DSAParams params;
-
- int n1, n2;
-
- byte[] bytes;
- byte[] digest;
-
- // checking up on signature's ASN1
- try {
- byte dummy;
- n1 = sigBytes[offset + 3];
- n2 = sigBytes[offset + n1 + 5];
-
- if (sigBytes[offset + 0] != 0x30 || sigBytes[offset + 2] != 2
- || sigBytes[offset + n1 + 4] != 2
- || sigBytes[offset + 1] != (n1 + n2 + 4) || n1 > 21
- || n2 > 21
- || (length != 0 && (sigBytes[offset + 1] + 2) > length)) {
- throw new SignatureException("signature bytes have invalid encoding");
- }
-
- dummy = sigBytes[5 + n1 + n2]; // to check length of sigBytes
- } catch (ArrayIndexOutOfBoundsException e) {
- throw new SignatureException("bad argument: byte[] is too small");
- }
-
- digest = msgDigest.digest();
-
- bytes = new byte[n1];
- System.arraycopy(sigBytes, offset + 4, bytes, 0, n1);
- r = new BigInteger(bytes);
-
- bytes = new byte[n2];
- System.arraycopy(sigBytes, offset + 6 + n1, bytes, 0, n2);
- s = new BigInteger(bytes);
-
- params = dsaKey.getParams();
- p = params.getP();
- q = params.getQ();
- g = params.getG();
- y = ((DSAPublicKey) dsaKey).getY();
-
- // forming signature according algorithm described in chapter 6 of DSA standard
-
- if (r.signum() != 1 || r.compareTo(q) != -1 || s.signum() != 1
- || s.compareTo(q) != -1) {
- return false;
- }
-
- w = s.modInverse(q);
-
- u1 = (new BigInteger(1, digest)).multiply(w).mod(q);
- u2 = r.multiply(w).mod(q);
-
- v = g.modPow(u1, p).multiply(y.modPow(u2, p)).mod(p).mod(q);
-
- if (v.compareTo(r) != 0) {
- return false;
- }
- return true;
- }
-
- /**
- * Verifies the signature bytes.
- *
- * @params
- * sigBytes byte array with signature bytes to verify.
- * @return
- * true if signature bytes were verified, false otherwise
- * @throws
- * SignatureException if object's state is not VERIFY or
- * signature format is not ASN1 representation or
- * signature algorithm cannot process data
- */
- protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
- if (sigBytes == null) {
- throw new NullPointerException("sigBytes == null");
- }
-
- return checkSignature(sigBytes, 0, 0);
- }
-
- /**
- * Verifies the signature bytes.
- *
- * @params
- * sigBytes byte array with signature bytes to verify.
- * @params
- * offset index in sigBytes to start from
- * @params
- * length number of bytes allotted for signature
- * @return
- * true if signature bytes were verified, false otherwise
- * @throws
- * SignatureException if object's state is not VERIFY or
- * signature format is not ASN1 representation or
- * signature algorithm cannot process data
- */
- protected boolean engineVerify(byte[] sigBytes, int offset, int length)
- throws SignatureException {
- return checkSignature(sigBytes, offset, length);
- }
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/ThreeIntegerSequence.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/ThreeIntegerSequence.java
deleted file mode 100644
index 4f4232a..0000000
--- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/ThreeIntegerSequence.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package org.apache.harmony.security.provider.crypto;
-
-import org.apache.harmony.security.asn1.ASN1Integer;
-import org.apache.harmony.security.asn1.ASN1Sequence;
-import org.apache.harmony.security.asn1.ASN1Type;
-import org.apache.harmony.security.asn1.BerInputStream;
-
-
-/**
- * The auxiliary class providing means to process ASN1Sequence of three Integers.
- * Such sequences are parts of ASN1 encoded formats for DSA private and public keys.
- */
-class ThreeIntegerSequence {
-
- byte[] p, q, g;
-
- private byte[] encoding;
-
- ThreeIntegerSequence(byte[] p, byte[] q, byte[] g) {
-
- this.p = p;
- this.q = q;
- this.g = g;
- encoding = null;
- }
-
- public byte[] getEncoded() {
- if (encoding == null) {
- encoding = ASN1.encode(this);
- }
- return encoding;
- }
-
- public static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {
- ASN1Integer.getInstance(), ASN1Integer.getInstance(),
- ASN1Integer.getInstance() }) {
-
- protected Object getDecodedObject(BerInputStream in) {
-
- Object[] values = (Object[]) in.content;
-
- return new ThreeIntegerSequence((byte[]) values[0],
- (byte[]) values[1], (byte[]) values[2]);
- }
-
- protected void getValues(Object object, Object[] values) {
-
- ThreeIntegerSequence mySeq = (ThreeIntegerSequence) object;
-
- values[0] = mySeq.p;
- values[1] = mySeq.q;
- values[2] = mySeq.g;
- }
- };
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java b/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java
index 135394d..e7f3596 100644
--- a/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java
+++ b/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java
@@ -21,6 +21,7 @@
*/
package org.apache.harmony.security.utils;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
@@ -30,10 +31,12 @@ import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.Signature;
import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.LinkedList;
import java.util.List;
import javax.security.auth.x500.X500Principal;
@@ -42,7 +45,6 @@ import org.apache.harmony.security.asn1.BerInputStream;
import org.apache.harmony.security.pkcs7.ContentInfo;
import org.apache.harmony.security.pkcs7.SignedData;
import org.apache.harmony.security.pkcs7.SignerInfo;
-import org.apache.harmony.security.provider.cert.X509CertImpl;
import org.apache.harmony.security.x501.AttributeTypeAndValue;
public class JarUtils {
@@ -53,27 +55,18 @@ public class JarUtils {
new int[] {1, 2, 840, 113549, 1, 9, 4};
/**
- * @see #verifySignature(InputStream, InputStream, boolean)
- */
- public static Certificate[] verifySignature(InputStream signature, InputStream signatureBlock)
- throws IOException, GeneralSecurityException {
- return verifySignature(signature, signatureBlock, false);
- }
-
- /**
* This method handle all the work with PKCS7, ASN1 encoding, signature verifying,
* and certification path building.
* See also PKCS #7: Cryptographic Message Syntax Standard:
* http://www.ietf.org/rfc/rfc2315.txt
* @param signature - the input stream of signature file to be verified
* @param signatureBlock - the input stream of corresponding signature block file
- * @param chainCheck - whether to validate certificate chain signatures
* @return array of certificates used to verify the signature file
* @throws IOException - if some errors occurs during reading from the stream
* @throws GeneralSecurityException - if signature verification process fails
*/
public static Certificate[] verifySignature(InputStream signature, InputStream
- signatureBlock, boolean chainCheck) throws IOException, GeneralSecurityException {
+ signatureBlock) throws IOException, GeneralSecurityException {
BerInputStream bis = new BerInputStream(signatureBlock);
ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
@@ -87,9 +80,13 @@ public class JarUtils {
return null;
}
X509Certificate[] certs = new X509Certificate[encCerts.size()];
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
int i = 0;
for (org.apache.harmony.security.x509.Certificate encCert : encCerts) {
- certs[i++] = new X509CertImpl(encCert);
+ final byte[] encoded = encCert.getEncoded();
+ final InputStream is = new ByteArrayInputStream(encoded);
+ certs[i++] = new VerbatimX509Certificate((X509Certificate) cf.generateCertificate(is),
+ encoded);
}
List<SignerInfo> sigInfos = signedData.getSignerInfos();
@@ -131,17 +128,16 @@ public class JarUtils {
String alg = null;
Signature sig = null;
- if (daOid != null && deaOid != null) {
- alg = daOid + "with" + deaOid;
+ if (deaOid != null) {
+ alg = deaOid;
try {
sig = Signature.getInstance(alg);
} catch (NoSuchAlgorithmException e) {
}
- // Try to convert to names instead of OID.
- if (sig == null) {
- final String deaName = sigInfo.getDigestEncryptionAlgorithmName();
- alg = daName + "with" + deaName;
+ final String deaName = sigInfo.getDigestEncryptionAlgorithmName();
+ if (sig == null && deaName != null) {
+ alg = deaName;
try {
sig = Signature.getInstance(alg);
} catch (NoSuchAlgorithmException e) {
@@ -149,19 +145,17 @@ public class JarUtils {
}
}
- /*
- * TODO figure out the case in which we'd only use digestAlgorithm and
- * add a test for it.
- */
- if (sig == null && daOid != null) {
- alg = daOid;
+ if (sig == null && daOid != null && deaOid != null) {
+ alg = daOid + "with" + deaOid;
try {
sig = Signature.getInstance(alg);
} catch (NoSuchAlgorithmException e) {
}
- if (sig == null && daName != null) {
- alg = daName;
+ // Try to convert to names instead of OID.
+ if (sig == null) {
+ final String deaName = sigInfo.getDigestEncryptionAlgorithmName();
+ alg = daName + "with" + deaName;
try {
sig = Signature.getInstance(alg);
} catch (NoSuchAlgorithmException e) {
@@ -232,54 +226,63 @@ public class JarUtils {
throw new SecurityException("Incorrect signature");
}
- return createChain(certs[issuerSertIndex], certs, chainCheck);
+ return createChain(certs[issuerSertIndex], certs);
}
- private static X509Certificate[] createChain(X509Certificate signer,
- X509Certificate[] candidates, boolean chainCheck) {
- LinkedList chain = new LinkedList();
- chain.add(0, signer);
+ private static X509Certificate[] createChain(X509Certificate signer,
+ X509Certificate[] candidates) {
+ Principal issuer = signer.getIssuerDN();
// Signer is self-signed
- if (signer.getSubjectDN().equals(signer.getIssuerDN())){
- return (X509Certificate[])chain.toArray(new X509Certificate[1]);
+ if (signer.getSubjectDN().equals(issuer)) {
+ return new X509Certificate[] { signer };
}
- Principal issuer = signer.getIssuerDN();
+ ArrayList<X509Certificate> chain = new ArrayList<X509Certificate>(candidates.length + 1);
+ chain.add(0, signer);
+
X509Certificate issuerCert;
- X509Certificate subjectCert = signer;
int count = 1;
while (true) {
- issuerCert = findCert(issuer, candidates, subjectCert, chainCheck);
- if( issuerCert == null) {
+ issuerCert = findCert(issuer, candidates);
+ if (issuerCert == null) {
break;
}
chain.add(issuerCert);
count++;
- if (issuerCert.getSubjectDN().equals(issuerCert.getIssuerDN())) {
+ issuer = issuerCert.getIssuerDN();
+ if (issuerCert.getSubjectDN().equals(issuer)) {
break;
}
- issuer = issuerCert.getIssuerDN();
- subjectCert = issuerCert;
}
- return (X509Certificate[])chain.toArray(new X509Certificate[count]);
+ return chain.toArray(new X509Certificate[count]);
}
- private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates,
- X509Certificate subjectCert, boolean chainCheck) {
+ private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates) {
for (int i = 0; i < candidates.length; i++) {
if (issuer.equals(candidates[i].getSubjectDN())) {
- if (chainCheck) {
- try {
- subjectCert.verify(candidates[i].getPublicKey());
- } catch (Exception e) {
- continue;
- }
- }
return candidates[i];
}
}
return null;
}
+ /**
+ * For legacy reasons we need to return exactly the original encoded
+ * certificate bytes, instead of letting the underlying implementation have
+ * a shot at re-encoding the data.
+ */
+ private static class VerbatimX509Certificate extends WrappedX509Certificate {
+ private byte[] encodedVerbatim;
+
+ public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
+ super(wrapped);
+ this.encodedVerbatim = encodedVerbatim;
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return encodedVerbatim;
+ }
+ }
}
diff --git a/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java b/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java
new file mode 100644
index 0000000..2b09309
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.security.utils;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+public class WrappedX509Certificate extends X509Certificate {
+ private final X509Certificate wrapped;
+
+ public WrappedX509Certificate(X509Certificate wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public Set<String> getCriticalExtensionOIDs() {
+ return wrapped.getCriticalExtensionOIDs();
+ }
+
+ @Override
+ public byte[] getExtensionValue(String oid) {
+ return wrapped.getExtensionValue(oid);
+ }
+
+ @Override
+ public Set<String> getNonCriticalExtensionOIDs() {
+ return wrapped.getNonCriticalExtensionOIDs();
+ }
+
+ @Override
+ public boolean hasUnsupportedCriticalExtension() {
+ return wrapped.hasUnsupportedCriticalExtension();
+ }
+
+ @Override
+ public void checkValidity() throws CertificateExpiredException,
+ CertificateNotYetValidException {
+ wrapped.checkValidity();
+ }
+
+ @Override
+ public void checkValidity(Date date) throws CertificateExpiredException,
+ CertificateNotYetValidException {
+ wrapped.checkValidity(date);
+ }
+
+ @Override
+ public int getVersion() {
+ return wrapped.getVersion();
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ return wrapped.getSerialNumber();
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ return wrapped.getIssuerDN();
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ return wrapped.getSubjectDN();
+ }
+
+ @Override
+ public Date getNotBefore() {
+ return wrapped.getNotBefore();
+ }
+
+ @Override
+ public Date getNotAfter() {
+ return wrapped.getNotAfter();
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ return wrapped.getTBSCertificate();
+ }
+
+ @Override
+ public byte[] getSignature() {
+ return wrapped.getSignature();
+ }
+
+ @Override
+ public String getSigAlgName() {
+ return wrapped.getSigAlgName();
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ return wrapped.getSigAlgOID();
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ return wrapped.getSigAlgParams();
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ return wrapped.getIssuerUniqueID();
+ }
+
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ return wrapped.getSubjectUniqueID();
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ return wrapped.getKeyUsage();
+ }
+
+ @Override
+ public int getBasicConstraints() {
+ return wrapped.getBasicConstraints();
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return wrapped.getEncoded();
+ }
+
+ @Override
+ public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+ InvalidKeyException, NoSuchProviderException, SignatureException {
+ wrapped.verify(key);
+ }
+
+ @Override
+ public void verify(PublicKey key, String sigProvider) throws CertificateException,
+ NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+ SignatureException {
+ verify(key, sigProvider);
+ }
+
+ @Override
+ public String toString() {
+ return wrapped.toString();
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ return wrapped.getPublicKey();
+ }
+}
diff --git a/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValue.java b/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValue.java
index 3b5f622..171d9c2 100644
--- a/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValue.java
+++ b/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValue.java
@@ -287,6 +287,8 @@ public final class AttributeTypeAndValue {
} else {
if (X500Principal.CANONICAL.equals(attrFormat)) {
sb.append(value.makeCanonical());
+ } else if (X500Principal.RFC2253.equals(attrFormat)) {
+ sb.append(value.getRFC2253String());
} else {
sb.append(value.escapedString);
}
diff --git a/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValueComparator.java b/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValueComparator.java
index 160c62d..bdc3c84 100644
--- a/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValueComparator.java
+++ b/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValueComparator.java
@@ -30,27 +30,26 @@ import org.apache.harmony.security.utils.ObjectIdentifier;
* AttributeTypeAndValue comparator
*
*/
-public class AttributeTypeAndValueComparator implements Comparator, Serializable {
+public class AttributeTypeAndValueComparator implements Comparator<AttributeTypeAndValue>,
+ Serializable {
private static final long serialVersionUID = -1286471842007103132L;
/**
* compares two AttributeTypeAndValues
*
- * @param obj1
+ * @param atav1
* first AttributeTypeAndValue
- * @param obj2
+ * @param atav2
* second AttributeTypeAndValue
* @return -1 of first AttributeTypeAndValue "less" than second
* AttributeTypeAndValue 1 otherwise, 0 if they are equal
*/
- public int compare(Object obj1, Object obj2) {
- if (obj1 == obj2) {
+ public int compare(AttributeTypeAndValue atav1, AttributeTypeAndValue atav2) {
+ if (atav1 == atav2) {
return 0;
}
- AttributeTypeAndValue atav1 = (AttributeTypeAndValue) obj1;
- AttributeTypeAndValue atav2 = (AttributeTypeAndValue) obj2;
String kw1 = atav1.getType().getName();
String kw2 = atav2.getType().getName();
if (kw1 != null && kw2 == null) {
diff --git a/luni/src/main/java/org/apache/harmony/security/x501/AttributeValue.java b/luni/src/main/java/org/apache/harmony/security/x501/AttributeValue.java
index 63be3f1..b3eb200 100644
--- a/luni/src/main/java/org/apache/harmony/security/x501/AttributeValue.java
+++ b/luni/src/main/java/org/apache/harmony/security/x501/AttributeValue.java
@@ -37,8 +37,12 @@ public final class AttributeValue {
public boolean wasEncoded;
+ private boolean hasConsecutiveSpaces;
+
public final String escapedString;
+ private String rfc2253String;
+
private String hexString;
private final int tag;
@@ -197,8 +201,9 @@ public final class AttributeValue {
* Escapes:
* 1) chars ",", "+", """, "\", "<", ">", ";" (RFC 2253)
* 2) chars "#", "=" (required by RFC 1779)
- * 3) a space char at the beginning or end
- * 4) according to the requirement to be RFC 1779 compatible:
+ * 3) leading or trailing spaces
+ * 4) consecutive spaces (RFC 1779)
+ * 5) according to the requirement to be RFC 1779 compatible:
* '#' char is escaped in any position
*/
private String makeEscaped(String name) {
@@ -208,14 +213,35 @@ public final class AttributeValue {
}
StringBuilder buf = new StringBuilder(length * 2);
+ // Keeps track of whether we are escaping spaces.
+ boolean escapeSpaces = false;
+
for (int index = 0; index < length; index++) {
char ch = name.charAt(index);
switch (ch) {
case ' ':
- if (index == 0 || index == (length - 1)) {
- // escape first or last space
+ /*
+ * We should escape spaces in the following cases:
+ * 1) at the beginning
+ * 2) at the end
+ * 3) consecutive spaces
+ * Since multiple spaces at the beginning or end will be covered by
+ * 3, we don't need a special case to check for that. Note that RFC 2253
+ * doesn't escape consecutive spaces, so they are removed in
+ * getRFC2253String instead of making two different strings here.
+ */
+ if (index < (length - 1)) {
+ boolean nextIsSpace = name.charAt(index + 1) == ' ';
+ escapeSpaces = escapeSpaces || nextIsSpace || index == 0;
+ hasConsecutiveSpaces |= nextIsSpace;
+ } else {
+ escapeSpaces = true;
+ }
+
+ if (escapeSpaces) {
buf.append('\\');
}
+
buf.append(' ');
break;
@@ -241,6 +267,10 @@ public final class AttributeValue {
buf.append(ch);
break;
}
+
+ if (escapeSpaces && ch != ' ') {
+ escapeSpaces = false;
+ }
}
return buf.toString();
@@ -295,4 +325,50 @@ public final class AttributeValue {
return buf.toString();
}
+
+ /**
+ * Removes escape sequences used in RFC1779 escaping but not in RFC2253 and
+ * returns the RFC2253 string to the caller..
+ */
+ public String getRFC2253String() {
+ if (!hasConsecutiveSpaces) {
+ return escapedString;
+ }
+
+ if (rfc2253String == null) {
+ // Scan backwards first since runs of spaces at the end are escaped.
+ int lastIndex = escapedString.length() - 2;
+ for (int i = lastIndex; i > 0; i -= 2) {
+ if (escapedString.charAt(i) == '\\' && escapedString.charAt(i + 1) == ' ') {
+ lastIndex = i - 1;
+ }
+ }
+
+ boolean beginning = true;
+ StringBuilder sb = new StringBuilder(escapedString.length());
+ for (int i = 0; i < escapedString.length(); i++) {
+ char ch = escapedString.charAt(i);
+ if (ch != '\\') {
+ sb.append(ch);
+ beginning = false;
+ } else {
+ char nextCh = escapedString.charAt(i + 1);
+ if (nextCh == ' ') {
+ if (beginning || i > lastIndex) {
+ sb.append(ch);
+ }
+ sb.append(nextCh);
+ } else {
+ sb.append(ch);
+ sb.append(nextCh);
+ beginning = false;
+ }
+
+ i++;
+ }
+ }
+ rfc2253String = sb.toString();
+ }
+ return rfc2253String;
+ }
}
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/Extension.java b/luni/src/main/java/org/apache/harmony/security/x509/Extension.java
index d9b02f9..d5d8015 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/Extension.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/Extension.java
@@ -23,6 +23,7 @@
package org.apache.harmony.security.x509;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.Arrays;
import org.apache.harmony.security.asn1.ASN1Boolean;
import org.apache.harmony.security.asn1.ASN1OctetString;
@@ -49,7 +50,7 @@ import org.apache.harmony.security.utils.Array;
* }
* </pre>
*/
-public final class Extension {
+public final class Extension implements java.security.cert.Extension {
// critical constants
public static final boolean CRITICAL = true;
public static final boolean NON_CRITICAL = false;
@@ -145,7 +146,8 @@ public final class Extension {
/**
* Returns the value of extnID field of the structure.
*/
- public String getExtnID() {
+ @Override
+ public String getId() {
if (extnID_str == null) {
extnID_str = ObjectIdentifier.toString(extnID);
}
@@ -155,14 +157,16 @@ public final class Extension {
/**
* Returns the value of critical field of the structure.
*/
- public boolean getCritical() {
+ @Override
+ public boolean isCritical() {
return critical;
}
/**
* Returns the value of extnValue field of the structure.
*/
- public byte[] getExtnValue() {
+ @Override
+ public byte[] getValue() {
return extnValue;
}
@@ -187,6 +191,11 @@ public final class Extension {
return encoding;
}
+ @Override
+ public void encode(OutputStream out) throws IOException {
+ out.write(getEncoded());
+ }
+
@Override public boolean equals(Object ext) {
if (!(ext instanceof Extension)) {
return false;
@@ -287,7 +296,7 @@ public final class Extension {
}
public void dumpValue(StringBuilder sb, String prefix) {
- sb.append("OID: ").append(getExtnID()).append(", Critical: ").append(critical).append('\n');
+ sb.append("OID: ").append(getId()).append(", Critical: ").append(critical).append('\n');
if (!valueDecoded) {
try {
decodeExtensionValue();
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java b/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java
index 92ff3a9..7a10ebc 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java
@@ -136,8 +136,8 @@ public final class Extensions {
Set<String> localNoncritical = new HashSet<String>(size);
Boolean localHasUnsupported = Boolean.FALSE;
for (Extension extension : extensions) {
- String oid = extension.getExtnID();
- if (extension.getCritical()) {
+ String oid = extension.getId();
+ if (extension.isCritical()) {
if (!SUPPORTED_CRITICAL.contains(oid)) {
localHasUnsupported = Boolean.TRUE;
}
@@ -162,7 +162,7 @@ public final class Extensions {
if (localOidMap == null) {
localOidMap = new HashMap<String, Extension>();
for (Extension extension : extensions) {
- localOidMap.put(extension.getExtnID(), extension);
+ localOidMap.put(extension.getId(), extension);
}
this.oidMap = localOidMap;
}
@@ -311,7 +311,7 @@ public final class Extensions {
}
Collection<List<?>> collection = ((GeneralNames) GeneralNames.ASN1.decode(extension
- .getExtnValue())).getPairsList();
+ .getValue())).getPairsList();
/*
* If the extension had any invalid entries, we may have an empty
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/InvalidityDate.java b/luni/src/main/java/org/apache/harmony/security/x509/InvalidityDate.java
index b7c1847..533c79c 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/InvalidityDate.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/InvalidityDate.java
@@ -44,6 +44,13 @@ public final class InvalidityDate extends ExtensionValue {
}
/**
+ * Constructs the object from a date instance.
+ */
+ public InvalidityDate(Date date) {
+ this.date = (Date) date.clone();
+ }
+
+ /**
* Returns the invalidity date.
*/
public Date getDate() {
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/ReasonCode.java b/luni/src/main/java/org/apache/harmony/security/x509/ReasonCode.java
index 183ecde..2c9d6aa 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/ReasonCode.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/ReasonCode.java
@@ -18,6 +18,7 @@
package org.apache.harmony.security.x509;
import java.io.IOException;
+import java.security.cert.CRLReason;
import org.apache.harmony.security.asn1.ASN1Enumerated;
import org.apache.harmony.security.asn1.ASN1Type;
@@ -71,6 +72,14 @@ public final class ReasonCode extends ExtensionValue {
return encoding;
}
+ public CRLReason getReason() {
+ CRLReason[] values = CRLReason.values();
+ if (code < 0 || code > values.length) {
+ return null;
+ }
+ return values[code];
+ }
+
@Override public void dumpValue(StringBuilder sb, String prefix) {
sb.append(prefix).append("Reason Code: [ ");
switch (code) {
diff --git a/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java b/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java
index db6f4ef..fa6308e 100644
--- a/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java
+++ b/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java
@@ -48,7 +48,7 @@ class ExpatParser {
private boolean inStartElement = false;
private int attributeCount = -1;
- private int attributePointer = 0;
+ private long attributePointer = 0;
private final Locator locator = new ExpatLocator();
@@ -129,7 +129,7 @@ class ExpatParser {
* @param attributeCount number of attributes
*/
/*package*/ void startElement(String uri, String localName, String qName,
- int attributePointer, int attributeCount) throws SAXException {
+ long attributePointer, int attributeCount) throws SAXException {
ContentHandler contentHandler = xmlReader.contentHandler;
if (contentHandler == null) {
return;
@@ -772,7 +772,7 @@ class ExpatParser {
@Override
void startElement(String uri, String localName, String qName,
- int attributePointer, int attributeCount) throws SAXException {
+ long attributePointer, int attributeCount) throws SAXException {
/*
* Skip topmost element generated by our workaround in
* {@link #handleExternalEntity}.