diff --git a/make/java/management/Exportedfiles.gmk b/make/java/management/Exportedfiles.gmk index 0654fb5752655c14a8c423abdd6fcbafb48d8f2b..21f46bd923a75917fed307ec756fb710ef1fe209 100644 --- a/make/java/management/Exportedfiles.gmk +++ b/make/java/management/Exportedfiles.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ FILES_export = \ sun/management/ClassLoadingImpl.java \ + sun/management/DiagnosticCommandImpl.java \ sun/management/FileSystemImpl.java \ sun/management/Flag.java \ sun/management/GarbageCollectorImpl.java \ diff --git a/make/java/management/FILES_c.gmk b/make/java/management/FILES_c.gmk index 8c5ddce29b5c17b860389841f949ed4917653b6c..f0d0c9c6e439a4ccab7adcc2650dd4d7bf101b73 100644 --- a/make/java/management/FILES_c.gmk +++ b/make/java/management/FILES_c.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ FILES_c = \ ClassLoadingImpl.c \ + DiagnosticCommandImpl.c \ FileSystemImpl.c \ Flag.c \ GarbageCollectorImpl.c \ diff --git a/make/java/management/mapfile-vers b/make/java/management/mapfile-vers index 2e87b70746386c67552285d66567c655f678c7ba..63c4fbf03ccd5d90fd7d45b6427f9a2ea4ec25d5 100644 --- a/make/java/management/mapfile-vers +++ b/make/java/management/mapfile-vers @@ -1,5 +1,5 @@ # -# Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,10 @@ SUNWprivate_1.1 { Java_com_sun_management_UnixOperatingSystem_getTotalSwapSpaceSize; Java_com_sun_management_UnixOperatingSystem_initialize; Java_sun_management_ClassLoadingImpl_setVerboseClass; + Java_sun_management_DiagnosticCommandImpl_executeDiagnosticCommand; + Java_sun_management_DiagnosticCommandImpl_getDiagnosticCommands; + Java_sun_management_DiagnosticCommandImpl_getDiagnosticCommandInfo; + Java_sun_management_DiagnosticCommandImpl_setNotificationEnabled; Java_sun_management_FileSystemImpl_isAccessUserOnly0; Java_sun_management_Flag_getAllFlagNames; Java_sun_management_Flag_getFlags; diff --git a/make/sun/awt/FILES_c_unix.gmk b/make/sun/awt/FILES_c_unix.gmk index 8259811158e1b2a247e96c37f86cd4087c7405a2..b8d0e05d2e6ea9895c5ba7167b1acfbdcdc1a4bc 100644 --- a/make/sun/awt/FILES_c_unix.gmk +++ b/make/sun/awt/FILES_c_unix.gmk @@ -171,3 +171,13 @@ FILES_NO_MOTIF_c = \ GLXSurfaceData.c \ AccelGlyphCache.c \ CUPSfuncs.c + +ifeq ($(PLATFORM), macosx) +FILES_NO_MOTIF_objc = \ + AWTFont.m \ + AWTStrike.m \ + CCharToGlyphMapper.m \ + CGGlyphImages.m \ + CGGlyphOutlines.m \ + CoreTextSupport.m +endif # PLATFORM diff --git a/make/sun/awt/FILES_export_unix.gmk b/make/sun/awt/FILES_export_unix.gmk index f572e19ee53d8f3eae096b4526eb8ceeaa4b8879..11f65e94506665524f1e8c1c5e7d530c29d91b82 100644 --- a/make/sun/awt/FILES_export_unix.gmk +++ b/make/sun/awt/FILES_export_unix.gmk @@ -187,3 +187,14 @@ FILES_export2 = \ java/awt/dnd/DnDConstants.java \ sun/awt/CausedFocusEvent.java +ifeq ($(PLATFORM), macosx) +ifeq ($(HEADLESS), true) +FILES_export += \ + sun/awt/SunHints.java \ + sun/font/CCharToGlyphMapper.java \ + sun/font/CFont.java \ + sun/font/CFontManager.java \ + sun/font/CStrike.java \ + sun/font/CStrikeDisposer.java +endif # HEADLESS +endif # PLATFORM diff --git a/make/sun/awt/mawt.gmk b/make/sun/awt/mawt.gmk index 5abf993d1d7d4dba56d04097666bfe2a0d56db74..7ca05207edef047fc6421c24be5f486248df9204 100644 --- a/make/sun/awt/mawt.gmk +++ b/make/sun/awt/mawt.gmk @@ -43,6 +43,10 @@ include $(BUILDDIR)/sun/awt/FILES_export_unix.gmk # compiled based on the motif version. FILES_c = $(FILES_NO_MOTIF_c) +ifeq ($(PLATFORM), macosx) +FILES_objc = $(FILES_NO_MOTIF_objc) +endif # PLATFORM + ifeq ($(PLATFORM), solaris) ifneq ($(ARCH), amd64) FILES_reorder += reorder-$(ARCH) @@ -97,6 +101,10 @@ vpath %.c $(SHARE_SRC)/native/$(PKGDIR)/../java2d/pipe vpath %.cpp $(SHARE_SRC)/native/$(PKGDIR)/image vpath %.c $(PLATFORM_SRC)/native/$(PKGDIR)/robot_child +ifeq ($(PLATFORM), macosx) +vpath %.m $(call NativeSrcDirList,,native/sun/font) +endif # PLATFORM + # # Libraries to link in. # @@ -192,13 +200,21 @@ CPPFLAGS += -I$(SHARE_SRC)/native/$(PKGDIR)/debug \ $(EVENT_MODEL) ifeq ($(PLATFORM), macosx) -CPPFLAGS += -I$(CUPS_HEADERS_PATH) +CPPFLAGS += -I$(CUPS_HEADERS_PATH) \ + $(call NativeSrcDirList,-I,native/sun/awt) \ + $(call NativeSrcDirList,-I,native/sun/font) ifndef HEADLESS CPPFLAGS += -I$(MOTIF_DIR)/include \ -I$(OPENWIN_HOME)/include LDFLAGS += -L$(MOTIF_LIB) -L$(OPENWIN_LIB) - +else +LDFLAGS += -framework Accelerate \ + -framework ApplicationServices \ + -framework Cocoa \ + -F/System/Library/Frameworks/JavaVM.framework/Frameworks \ + -framework JavaNativeFoundation \ + -framework JavaRuntimeSupport endif # !HEADLESS endif # PLATFORM diff --git a/make/tools/CharsetMapping/EUC_KR.map b/make/tools/CharsetMapping/EUC_KR.map index 0b44e6c91c9e8345fb7afd2eea23fcb90bc5aeec..d1fae60e1aa5b24ccdf90f2c20ef1d9bee4f18a7 100644 --- a/make/tools/CharsetMapping/EUC_KR.map +++ b/make/tools/CharsetMapping/EUC_KR.map @@ -5,6 +5,8 @@ # (2)Added 2 new codepoints (KS X 1001:1998) # 0xA2E6 0x20AC # EURO Sign # 0xA2E7 0x00AE # Registered Sign +# (3) KS X 1001:2002 +# 0xA2E8 0x327E # CIRCLED KOREAN CHARACTER JUEUI (Postal Code Mark) # 0x00 0x0000 0x01 0x0001 @@ -295,6 +297,7 @@ # 0xA2E6 0x20AC # EURO Sign 0xA2E7 0x00AE # Registered Sign +0xA2E8 0x327E # CIRCLED KOREAN CHARACTER JUEUI # 0xA2E0 0x2116 # NUMERO SIGN 0xA2E1 0x33C7 # SQUARE CO diff --git a/make/tools/src/build/tools/generatebreakiteratordata/CharSet.java b/make/tools/src/build/tools/generatebreakiteratordata/CharSet.java index 2dc948a66415da8c60b5e4a536416ec6e2f8386c..b8539159a8c4341d954a2f9b8771c22a07975cc1 100644 --- a/make/tools/src/build/tools/generatebreakiteratordata/CharSet.java +++ b/make/tools/src/build/tools/generatebreakiteratordata/CharSet.java @@ -39,6 +39,7 @@ package build.tools.generatebreakiteratordata; +import java.util.Arrays; import java.util.Hashtable; /** @@ -701,7 +702,14 @@ class CharSet { * the exact same characters as this one */ public boolean equals(Object that) { - return (that instanceof CharSet) && chars.equals(((CharSet)that).chars); + return (that instanceof CharSet) && Arrays.equals(chars, ((CharSet)that).chars); + } + + /** + * Returns the hash code for this set of characters + */ + public int hashCode() { + return Arrays.hashCode(chars); } /** diff --git a/makefiles/CompileJavaClasses.gmk b/makefiles/CompileJavaClasses.gmk index 24ce0d922f03699330f6fb6c0d51993b57b4b163..0b3e70dd9146f77602de18164c040a742a8e92c7 100644 --- a/makefiles/CompileJavaClasses.gmk +++ b/makefiles/CompileJavaClasses.gmk @@ -342,7 +342,7 @@ $(eval $(call SetupJavaCompilation,BUILD_JOBJC,\ DISABLE_SJAVAC:=true,\ SRC:=$(JDK_TOPDIR)/src/macosx/native/jobjc/src/core/java \ $(JDK_TOPDIR)/src/macosx/native/jobjc/src/runtime-additions/java \ - $(JDK_OUTPUTDIR)/gensrc, \ + $(JDK_OUTPUTDIR)/gensrc_jobjc/src, \ INCLUDES := com/apple/jobjc,\ EXCLUDES := tests/java/com/apple/jobjc,\ BIN:=$(JDK_OUTPUTDIR)/jobjc_classes,\ @@ -355,7 +355,7 @@ $(eval $(call SetupJavaCompilation,BUILD_JOBJC_HEADERS,\ SETUP:=GENERATE_JDKBYTECODE,\ SRC:=$(JDK_TOPDIR)/src/macosx/native/jobjc/src/core/java \ $(JDK_TOPDIR)/src/macosx/native/jobjc/src/runtime-additions/java \ - $(JDK_OUTPUTDIR)/gensrc, \ + $(JDK_OUTPUTDIR)/gensrc_jobjc/src, \ INCLUDES := com/apple/jobjc,\ EXCLUDES := tests/java/com/apple/jobjc,\ BIN:=$(JDK_OUTPUTDIR)/jobjc_classes_headers,\ diff --git a/makefiles/CompileNativeLibraries.gmk b/makefiles/CompileNativeLibraries.gmk index 1836cbb13f9e670288dd9c7aa987968e1064bbb2..c30388eb9b638d9ba2f8b222b9e61e1e85b639fc 100644 --- a/makefiles/CompileNativeLibraries.gmk +++ b/makefiles/CompileNativeLibraries.gmk @@ -2314,6 +2314,10 @@ LIBAWT_HEADLESS_DIRS:=$(JDK_TOPDIR)/src/share/native/sun/font \ $(JDK_TOPDIR)/src/solaris/native/sun/java2d/opengl \ $(JDK_TOPDIR)/src/solaris/native/sun/java2d/x11 +ifeq ($(OPENJDK_TARGET_OS),macosx) + LIBAWT_HEADLESS_DIRS+=$(JDK_TOPDIR)/src/macosx/native/sun/font +endif + LIBAWT_HEADLESS_CFLAGS:=-DHEADLESS=true \ -DX11_PATH=\"$(X11_PATH)\" -DPACKAGE_PATH=\"$(PACKAGE_PATH)\" \ $(CUPS_CFLAGS) \ @@ -2328,6 +2332,12 @@ LIBAWT_HEADLESS_CFLAGS:=-DHEADLESS=true \ -I$(JDK_TOPDIR)/src/$(OPENJDK_TARGET_OS_API_DIR)/native/sun/jdga \ $(foreach dir,$(LIBAWT_HEADLESS_DIRS),-I$(dir)) +ifeq ($(OPENJDK_TARGET_OS),macosx) + LIBAWT_HEADLESS_CFLAGS+=\ + -F/System/Library/Frameworks/JavaVM.framework/Frameworks \ + -F/System/Library/Frameworks/ApplicationServices.framework/Frameworks +endif + LIBAWT_HEADLESS_FILES:=\ awt_Font.c \ HeadlessToolkit.c \ @@ -2356,6 +2366,16 @@ LIBAWT_HEADLESS_FILES:=\ AccelGlyphCache.c \ CUPSfuncs.c +ifeq ($(OPENJDK_TARGET_OS),macosx) + LIBAWT_HEADLESS_FILES+=\ + AWTFont.m \ + AWTStrike.m \ + CCharToGlyphMapper.m \ + CGGlyphImages.m \ + CGGlyphOutlines.m \ + CoreTextSupport.m +endif + LIBAWT_HEADLESS_REORDER:= ifeq ($(OPENJDK_TARGET_OS), solaris) ifneq ($(OPENJDK_TARGET_CPU), x86_64) @@ -2382,7 +2402,13 @@ $(eval $(call SetupNativeCompilation,BUILD_LIBAWT_HEADLESS,\ REORDER:=$(LIBAWT_HEADLESS_REORDER), \ LDFLAGS_SUFFIX_linux:=-ljvm -lawt -lm $(LIBDL) -ljava,\ LDFLAGS_SUFFIX_solaris:=$(LIBDL) -ljvm -lawt -lm -ljava $(LIBCXX) -lc,\ - LDFLAGS_SUFFIX_macosx:=-ljvm $(LIBCXX) -lawt $(LIBDL) -ljava,\ + LDFLAGS_SUFFIX_macosx:=-ljvm $(LIBCXX) -lawt $(LIBDL) -ljava \ + -framework Accelerate \ + -framework ApplicationServices \ + -framework Cocoa \ + -F/System/Library/Frameworks/JavaVM.framework/Frameworks \ + -framework JavaNativeFoundation \ + -framework JavaRuntimeSupport,\ OBJECT_DIR:=$(JDK_OUTPUTDIR)/objs/libawt_headless,\ DEBUG_SYMBOLS:=$(DEBUG_ALL_BINARIES))) diff --git a/makefiles/GensrcBuffer.gmk b/makefiles/GensrcBuffer.gmk index 3e55b1c14a45102a70bb0733c5013878ada24c63..55b51d050d8092658e831eb280b424ed55d28f06 100644 --- a/makefiles/GensrcBuffer.gmk +++ b/makefiles/GensrcBuffer.gmk @@ -69,6 +69,9 @@ define typesAndBits $1_fulltype := character $1_Fulltype := Character $1_category := integralType + $1_streams := streamableType + $1_streamtype := int + $1_Streamtype := Int $1_LBPV := 1 endif @@ -97,7 +100,7 @@ define typesAndBits $1_Type := Long $1_fulltype := long $1_Fulltype := Long - $1_category := integralType + $1_category := integralType $1_LBPV := 3 endif @@ -231,10 +234,13 @@ $$($1_DST) : $$($1_DEP) $(GENSRC_BUFFER_DST)/_the.buffer.dir $(TOOL_SPP) < $$($1_SRC) > $$($1_OUT).tmp \ -K$$($1_type) \ -K$$($1_category) \ + -K$$($1_streams) \ -Dtype=$$($1_type) \ -DType=$$($1_Type) \ -Dfulltype=$$($1_fulltype) \ -DFulltype=$$($1_Fulltype) \ + -Dstreamtype=$$($1_streamtype) \ + -DStreamtype=$$($1_Streamtype) \ -Dx=$$($1_x) \ -Dmemtype=$$($1_memtype) \ -DMemtype=$$($1_Memtype) \ diff --git a/makefiles/mapfiles/libmanagement/mapfile-vers b/makefiles/mapfiles/libmanagement/mapfile-vers index 2e87b70746386c67552285d66567c655f678c7ba..b934fe8b7487b6cbdbeeba9c83dfc2c1dedb062d 100644 --- a/makefiles/mapfiles/libmanagement/mapfile-vers +++ b/makefiles/mapfiles/libmanagement/mapfile-vers @@ -1,5 +1,5 @@ # -# Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,10 @@ SUNWprivate_1.1 { Java_com_sun_management_UnixOperatingSystem_getTotalSwapSpaceSize; Java_com_sun_management_UnixOperatingSystem_initialize; Java_sun_management_ClassLoadingImpl_setVerboseClass; + Java_sun_management_DiagnosticCommandImpl_executeDiagnosticCommand; + Java_sun_management_DiagnosticCommandImpl_getDiagnosticCommands; + Java_sun_management_DiagnosticCommandImpl_getDiagnosticCommandInfo; + Java_sun_management_DiagnosticCommandImpl_setNotificationEnabled; Java_sun_management_FileSystemImpl_isAccessUserOnly0; Java_sun_management_Flag_getAllFlagNames; Java_sun_management_Flag_getFlags; diff --git a/src/macosx/bin/java_md_macosx.c b/src/macosx/bin/java_md_macosx.c index 4fba1ede4b51159e3b0f5896ccdb15054daf5717..3aeb018caf37e5c6b213b53670d2d9091e74b957 100644 --- a/src/macosx/bin/java_md_macosx.c +++ b/src/macosx/bin/java_md_macosx.c @@ -44,7 +44,6 @@ #include #include #include -#include #include #include @@ -1001,6 +1000,32 @@ SetXStartOnFirstThreadArg() setenv(envVar, "1", 1); } +/* This class is made for performSelectorOnMainThread when java main + * should be launched on main thread. + * We cannot use dispatch_sync here, because it blocks the main dispatch queue + * which is used inside Cocoa + */ +@interface JavaLaunchHelper : NSObject { + int _returnValue; +} +- (void) launchJava:(NSValue*)argsValue; +- (int) getReturnValue; +@end + +@implementation JavaLaunchHelper + +- (void) launchJava:(NSValue*)argsValue +{ + _returnValue = JavaMain([argsValue pointerValue]); +} + +- (int) getReturnValue +{ + return _returnValue; +} + +@end + // MacOSX we may continue in the same thread int JVMInit(InvocationFunctions* ifn, jlong threadStackSize, @@ -1010,16 +1035,22 @@ JVMInit(InvocationFunctions* ifn, jlong threadStackSize, JLI_TraceLauncher("In same thread\n"); // need to block this thread against the main thread // so signals get caught correctly - __block int rslt; - dispatch_sync(dispatch_get_main_queue(), ^(void) { - JavaMainArgs args; - args.argc = argc; - args.argv = argv; - args.mode = mode; - args.what = what; - args.ifn = *ifn; - rslt = JavaMain((void*)&args); - }); + JavaMainArgs args; + args.argc = argc; + args.argv = argv; + args.mode = mode; + args.what = what; + args.ifn = *ifn; + int rslt; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + { + JavaLaunchHelper* launcher = [[[JavaLaunchHelper alloc] init] autorelease]; + [launcher performSelectorOnMainThread:@selector(launchJava:) + withObject:[NSValue valueWithPointer:(void*)&args] + waitUntilDone:YES]; + rslt = [launcher getReturnValue]; + } + [pool drain]; return rslt; } else { return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret); diff --git a/src/macosx/classes/sun/lwawt/macosx/CDropTargetContextPeer.java b/src/macosx/classes/sun/lwawt/macosx/CDropTargetContextPeer.java index 0f32d3178fd326c4db6ac04f0ac4884bdc23bd60..ad6171fe67ad919b93304d17677f92026d74dda9 100644 --- a/src/macosx/classes/sun/lwawt/macosx/CDropTargetContextPeer.java +++ b/src/macosx/classes/sun/lwawt/macosx/CDropTargetContextPeer.java @@ -38,7 +38,7 @@ final class CDropTargetContextPeer extends SunDropTargetContextPeer { private long fNativeDropTransfer = 0; private long fNativeDataAvailable = 0; private Object fNativeData = null; - private boolean insideTarget = false; + private boolean insideTarget = true; Object awtLockAccess = new Object(); diff --git a/src/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java b/src/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java index 42dfd8a5fb9a6ed85173a3b76907d8f9448ca7d4..0b9321de7917c9e50d637677c726fdff3691b273 100644 --- a/src/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java +++ b/src/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java @@ -115,6 +115,8 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo static final int RESIZABLE = 1 << 9; // both a style bit and prop bit static final int NONACTIVATING = 1 << 24; + static final int IS_DIALOG = 1 << 25; + static final int IS_MODAL = 1 << 26; static final int _STYLE_PROP_BITMASK = DECORATED | TEXTURED | UNIFIED | UTILITY | HUD | SHEET | CLOSEABLE | MINIMIZABLE | RESIZABLE; @@ -376,6 +378,13 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo } } + if (isDialog) { + styleBits = SET(styleBits, IS_DIALOG, true); + if (((Dialog) target).isModal()) { + styleBits = SET(styleBits, IS_MODAL, true); + } + } + peer.setTextured(IS(TEXTURED, styleBits)); return styleBits; diff --git a/src/macosx/classes/sun/lwawt/macosx/CPrinterJob.java b/src/macosx/classes/sun/lwawt/macosx/CPrinterJob.java index e198024dd0c88183e23e39210755a17630205197..b54a1baa2c5aed8150b7947082fbe1107f5bcf25 100644 --- a/src/macosx/classes/sun/lwawt/macosx/CPrinterJob.java +++ b/src/macosx/classes/sun/lwawt/macosx/CPrinterJob.java @@ -36,6 +36,7 @@ import java.security.PrivilegedAction; import javax.print.*; import javax.print.attribute.PrintRequestAttributeSet; import javax.print.attribute.HashPrintRequestAttributeSet; +import javax.print.attribute.standard.PageRanges; import sun.java2d.*; import sun.print.*; @@ -173,6 +174,19 @@ public class CPrinterJob extends RasterPrinterJob { if (nsPrintInfo != null) { fNSPrintInfo = nsPrintInfo.getValue(); } + + PageRanges pageRangesAttr = (PageRanges)attributes.get(PageRanges.class); + if (isSupportedValue(pageRangesAttr, attributes)) { + SunPageSelection rangeSelect = (SunPageSelection)attributes.get(SunPageSelection.class); + // If rangeSelect is not null, we are using AWT's print dialog that has + // All, Selection, and Range radio buttons + if (rangeSelect == null || rangeSelect == SunPageSelection.RANGE) { + int[][] range = pageRangesAttr.getMembers(); + // setPageRange will set firstPage and lastPage as called in getFirstPage + // and getLastPage + setPageRange(range[0][0] - 1, range[0][1] - 1); + } + } } volatile boolean onEventThread; @@ -225,7 +239,6 @@ public class CPrinterJob extends RasterPrinterJob { * the end of the document. Note that firstPage * and lastPage are 0 based page indices. */ - int numPages = mDocument.getNumberOfPages(); int firstPage = getFirstPage(); int lastPage = getLastPage(); @@ -242,42 +255,53 @@ public class CPrinterJob extends RasterPrinterJob { userCancelled = false; } - if (EventQueue.isDispatchThread()) { - // This is an AWT EventQueue, and this print rendering loop needs to block it. + //Add support for PageRange + PageRanges pr = (attributes == null) ? null + : (PageRanges)attributes.get(PageRanges.class); + int[][] prMembers = (pr == null) ? new int[0][0] : pr.getMembers(); + int loopi = 0; + do { + if (EventQueue.isDispatchThread()) { + // This is an AWT EventQueue, and this print rendering loop needs to block it. + + onEventThread = true; + + printingLoop = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public SecondaryLoop run() { + return Toolkit.getDefaultToolkit() + .getSystemEventQueue() + .createSecondaryLoop(); + } + }); - onEventThread = true; - - printingLoop = AccessController.doPrivileged(new PrivilegedAction() { - @Override - public SecondaryLoop run() { - return Toolkit.getDefaultToolkit() - .getSystemEventQueue() - .createSecondaryLoop(); + try { + // Fire off the print rendering loop on the AppKit thread, and don't have + // it wait and block this thread. + if (printLoop(false, firstPage, lastPage)) { + // Start a secondary loop on EDT until printing operation is finished or cancelled + printingLoop.enter(); + } + } catch (Exception e) { + e.printStackTrace(); } - }); + } else { + // Fire off the print rendering loop on the AppKit, and block this thread + // until it is done. + // But don't actually block... we need to come back here! + onEventThread = false; - try { - // Fire off the print rendering loop on the AppKit thread, and don't have - // it wait and block this thread. - if (printLoop(false, firstPage, lastPage)) { - // Start a secondary loop on EDT until printing operation is finished or cancelled - printingLoop.enter(); + try { + printLoop(true, firstPage, lastPage); + } catch (Exception e) { + e.printStackTrace(); } - } catch (Exception e) { - e.printStackTrace(); } - } else { - // Fire off the print rendering loop on the AppKit, and block this thread - // until it is done. - // But don't actually block... we need to come back here! - onEventThread = false; - - try { - printLoop(true, firstPage, lastPage); - } catch (Exception e) { - e.printStackTrace(); + if (++loopi < prMembers.length) { + firstPage = prMembers[loopi][0]-1; + lastPage = prMembers[loopi][1] -1; } - } + } while (loopi < prMembers.length); } finally { synchronized (this) { // NOTE: Native code shouldn't allow exceptions out while diff --git a/src/macosx/native/sun/awt/AWTWindow.m b/src/macosx/native/sun/awt/AWTWindow.m index bac24ff8d525844b9824841e5030b46e389f4f53..4a3a4720d0285fcc953f5a665d9bab9a96f69136 100644 --- a/src/macosx/native/sun/awt/AWTWindow.m +++ b/src/macosx/native/sun/awt/AWTWindow.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -536,8 +536,12 @@ AWT_ASSERT_APPKIT_THREAD; - (void) windowDidBecomeKey: (NSNotification *) notification { AWT_ASSERT_APPKIT_THREAD; [AWTToolkit eventCountPlusPlus]; - [CMenuBar activate:self.javaMenuBar modallyDisabled:NO]; AWTWindow *opposite = [AWTWindow lastKeyWindow]; + if (!IS(self.styleBits, IS_DIALOG)) { + [CMenuBar activate:self.javaMenuBar modallyDisabled:NO]; + } else if (IS(self.styleBits, IS_MODAL)) { + [CMenuBar activate:opposite->javaMenuBar modallyDisabled:YES]; + } [AWTWindow setLastKeyWindow:nil]; [self _deliverWindowFocusEvent:YES oppositeWindow: opposite]; diff --git a/src/macosx/native/sun/font/AWTFont.m b/src/macosx/native/sun/font/AWTFont.m index 20279c200b1d3a8309b66054b34494440eee8fe0..21716e608238f69b84cd4168ad31cd922be145da 100644 --- a/src/macosx/native/sun/font/AWTFont.m +++ b/src/macosx/native/sun/font/AWTFont.m @@ -395,6 +395,7 @@ JNF_COCOA_EXIT(env); #pragma mark --- Miscellaneous JNI --- +#ifndef HEADLESS /* * Class: sun_awt_PlatformFont * Method: initIDs @@ -416,3 +417,4 @@ Java_sun_awt_FontDescriptor_initIDs (JNIEnv *env, jclass cls) { } +#endif diff --git a/src/share/classes/com/sun/beans/finder/AbstractFinder.java b/src/share/classes/com/sun/beans/finder/AbstractFinder.java index 8ec9ffb7044ecfb67e4f691e798b97e46cc01f04..b7a6af88a3993b475c249292c543175efcc7fae3 100644 --- a/src/share/classes/com/sun/beans/finder/AbstractFinder.java +++ b/src/share/classes/com/sun/beans/finder/AbstractFinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,9 @@ */ package com.sun.beans.finder; +import java.lang.reflect.Executable; +import java.lang.reflect.Modifier; + import java.util.HashMap; import java.util.Map; @@ -37,7 +40,7 @@ import java.util.Map; * * @author Sergey A. Malenkov */ -abstract class AbstractFinder { +abstract class AbstractFinder { private final Class[] args; /** @@ -52,27 +55,6 @@ abstract class AbstractFinder { this.args = args; } - /** - * Returns an array of {@code Class} objects - * that represent the formal parameter types of the method. - * Returns an empty array if the method takes no parameters. - * - * @param method the object that represents method - * @return the parameter types of the method - */ - protected abstract Class[] getParameters(T method); - - /** - * Returns {@code true} if and only if the method - * was declared to take a variable number of arguments. - * - * @param method the object that represents method - * @return {@code true} if the method was declared - * to take a variable number of arguments; - * {@code false} otherwise - */ - protected abstract boolean isVarArgs(T method); - /** * Checks validness of the method. * At least the valid method should be public. @@ -81,7 +63,9 @@ abstract class AbstractFinder { * @return {@code true} if the method is valid, * {@code false} otherwise */ - protected abstract boolean isValid(T method); + protected boolean isValid(T method) { + return Modifier.isPublic(method.getModifiers()); + } /** * Performs a search in the {@code methods} array. @@ -109,7 +93,7 @@ abstract class AbstractFinder { for (T newMethod : methods) { if (isValid(newMethod)) { - Class[] newParams = getParameters(newMethod); + Class[] newParams = newMethod.getParameterTypes(); if (newParams.length == this.args.length) { PrimitiveWrapperMap.replacePrimitivesWithWrappers(newParams); if (isAssignable(newParams, this.args)) { @@ -120,6 +104,11 @@ abstract class AbstractFinder { boolean useNew = isAssignable(oldParams, newParams); boolean useOld = isAssignable(newParams, oldParams); + if (useOld && useNew) { + // only if parameters are equal + useNew = !newMethod.isSynthetic(); + useOld = !oldMethod.isSynthetic(); + } if (useOld == useNew) { ambiguous = true; } else if (useNew) { @@ -130,7 +119,7 @@ abstract class AbstractFinder { } } } - if (isVarArgs(newMethod)) { + if (newMethod.isVarArgs()) { int length = newParams.length - 1; if (length <= this.args.length) { Class[] array = new Class[this.args.length]; @@ -160,6 +149,11 @@ abstract class AbstractFinder { boolean useNew = isAssignable(oldParams, newParams); boolean useOld = isAssignable(newParams, oldParams); + if (useOld && useNew) { + // only if parameters are equal + useNew = !newMethod.isSynthetic(); + useOld = !oldMethod.isSynthetic(); + } if (useOld == useNew) { if (oldParams == map.get(oldMethod)) { ambiguous = true; diff --git a/src/share/classes/com/sun/beans/finder/ConstructorFinder.java b/src/share/classes/com/sun/beans/finder/ConstructorFinder.java index f7419c7973af7aafc68e9f710730f2fabd030ebe..1de6b0a8263fe1f91a8478df65faab3785e8af7b 100644 --- a/src/share/classes/com/sun/beans/finder/ConstructorFinder.java +++ b/src/share/classes/com/sun/beans/finder/ConstructorFinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -86,44 +86,4 @@ public final class ConstructorFinder extends AbstractFinder> { private ConstructorFinder(Class[] args) { super(args); } - - /** - * Returns an array of {@code Class} objects - * that represent the formal parameter types of the constructor. - * Returns an empty array if the constructor takes no parameters. - * - * @param constructor the object that represents constructor - * @return the parameter types of the constructor - */ - @Override - protected Class[] getParameters(Constructor constructor) { - return constructor.getParameterTypes(); - } - - /** - * Returns {@code true} if and only if the constructor - * was declared to take a variable number of arguments. - * - * @param constructor the object that represents constructor - * @return {@code true} if the constructor was declared - * to take a variable number of arguments; - * {@code false} otherwise - */ - @Override - protected boolean isVarArgs(Constructor constructor) { - return constructor.isVarArgs(); - } - - /** - * Checks validness of the constructor. - * The valid constructor should be public. - * - * @param constructor the object that represents constructor - * @return {@code true} if the constructor is valid, - * {@code false} otherwise - */ - @Override - protected boolean isValid(Constructor constructor) { - return Modifier.isPublic(constructor.getModifiers()); - } } diff --git a/src/share/classes/com/sun/beans/finder/MethodFinder.java b/src/share/classes/com/sun/beans/finder/MethodFinder.java index 98c52bc929a42c14b77450093db8ef014a927284..83a3cedbf3371e4edecfa697eca7606e38cf2936 100644 --- a/src/share/classes/com/sun/beans/finder/MethodFinder.java +++ b/src/share/classes/com/sun/beans/finder/MethodFinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -195,33 +195,6 @@ public final class MethodFinder extends AbstractFinder { this.name = name; } - /** - * Returns an array of {@code Class} objects - * that represent the formal parameter types of the method. - * Returns an empty array if the method takes no parameters. - * - * @param method the object that represents method - * @return the parameter types of the method - */ - @Override - protected Class[] getParameters(Method method) { - return method.getParameterTypes(); - } - - /** - * Returns {@code true} if and only if the method - * was declared to take a variable number of arguments. - * - * @param method the object that represents method - * @return {@code true} if the method was declared - * to take a variable number of arguments; - * {@code false} otherwise - */ - @Override - protected boolean isVarArgs(Method method) { - return method.isVarArgs(); - } - /** * Checks validness of the method. * The valid method should be public and @@ -233,6 +206,6 @@ public final class MethodFinder extends AbstractFinder { */ @Override protected boolean isValid(Method method) { - return !method.isBridge() && Modifier.isPublic(method.getModifiers()) && method.getName().equals(this.name); + return super.isValid(method) && method.getName().equals(this.name); } } diff --git a/src/share/classes/com/sun/crypto/provider/DHKeyAgreement.java b/src/share/classes/com/sun/crypto/provider/DHKeyAgreement.java index 33fa49297dd4022b6994936709cf5b92a3c510b7..6a2f298fe6e27b1d75585dca49655769ed054d74 100644 --- a/src/share/classes/com/sun/crypto/provider/DHKeyAgreement.java +++ b/src/share/classes/com/sun/crypto/provider/DHKeyAgreement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -403,8 +403,9 @@ extends KeyAgreementSpi { } return skey; } else if (algorithm.equals("TlsPremasterSecret")) { - // return entire secret - return new SecretKeySpec(secret, "TlsPremasterSecret"); + // remove leading zero bytes per RFC 5246 Section 8.1.2 + return new SecretKeySpec( + KeyUtil.trimZeroes(secret), "TlsPremasterSecret"); } else { throw new NoSuchAlgorithmException("Unsupported secret key " + "algorithm: "+ algorithm); diff --git a/src/share/classes/com/sun/crypto/provider/HmacPKCS12PBESHA1.java b/src/share/classes/com/sun/crypto/provider/HmacPKCS12PBESHA1.java index d41b88c7e38fbc8c37c6d9cd20c1558b9bd93c42..e85b8816a28c86e7572ab9ba7aed1a3171946378 100644 --- a/src/share/classes/com/sun/crypto/provider/HmacPKCS12PBESHA1.java +++ b/src/share/classes/com/sun/crypto/provider/HmacPKCS12PBESHA1.java @@ -86,12 +86,13 @@ public final class HmacPKCS12PBESHA1 extends HmacCore { throw new InvalidKeyException("SecretKey of PBE type required"); } if (params == null) { - // generate default for salt and iteration count if necessary - if (salt == null) { - salt = new byte[20]; - SunJCE.getRandom().nextBytes(salt); + // should not auto-generate default values since current + // javax.crypto.Mac api does not have any method for caller to + // retrieve the generated defaults. + if ((salt == null) || (iCount == 0)) { + throw new InvalidAlgorithmParameterException + ("PBEParameterSpec required for salt and iteration count"); } - if (iCount == 0) iCount = 100; } else if (!(params instanceof PBEParameterSpec)) { throw new InvalidAlgorithmParameterException ("PBEParameterSpec type required"); diff --git a/src/share/classes/com/sun/crypto/provider/PBMAC1Core.java b/src/share/classes/com/sun/crypto/provider/PBMAC1Core.java index 47663943184278c0e21e30c2ca60204433c5fae2..2f0ba131a55447112b6f5ff9c03e11582fe772cf 100644 --- a/src/share/classes/com/sun/crypto/provider/PBMAC1Core.java +++ b/src/share/classes/com/sun/crypto/provider/PBMAC1Core.java @@ -42,12 +42,10 @@ import java.security.spec.*; */ abstract class PBMAC1Core extends HmacCore { - private static final int DEFAULT_SALT_LENGTH = 20; - private static final int DEFAULT_COUNT = 4096; - + // NOTE: this class inherits the Cloneable interface from HmacCore + // Need to override clone() if mutable fields are added. private final String kdfAlgo; private final String hashAlgo; - private final PBKDF2Core kdf; private final int blockLength; // in octets /** @@ -56,13 +54,15 @@ abstract class PBMAC1Core extends HmacCore { */ PBMAC1Core(String kdfAlgo, String hashAlgo, int blockLength) throws NoSuchAlgorithmException { - super(hashAlgo, blockLength); this.kdfAlgo = kdfAlgo; this.hashAlgo = hashAlgo; this.blockLength = blockLength; + } - switch(kdfAlgo) { + private static PBKDF2Core getKDFImpl(String algo) { + PBKDF2Core kdf = null; + switch(algo) { case "HmacSHA1": kdf = new PBKDF2Core.HmacSHA1(); break; @@ -79,9 +79,10 @@ abstract class PBMAC1Core extends HmacCore { kdf = new PBKDF2Core.HmacSHA512(); break; default: - throw new NoSuchAlgorithmException( - "No MAC implementation for " + kdfAlgo); + throw new ProviderException( + "No MAC implementation for " + algo); } + return kdf; } /** @@ -120,12 +121,13 @@ abstract class PBMAC1Core extends HmacCore { throw new InvalidKeyException("SecretKey of PBE type required"); } if (params == null) { - // generate default for salt and iteration count if necessary - if (salt == null) { - salt = new byte[DEFAULT_SALT_LENGTH]; - SunJCE.getRandom().nextBytes(salt); + // should not auto-generate default values since current + // javax.crypto.Mac api does not have any method for caller to + // retrieve the generated defaults. + if ((salt == null) || (iCount == 0)) { + throw new InvalidAlgorithmParameterException + ("PBEParameterSpec required for salt and iteration count"); } - if (iCount == 0) iCount = DEFAULT_COUNT; } else if (!(params instanceof PBEParameterSpec)) { throw new InvalidAlgorithmParameterException ("PBEParameterSpec type required"); @@ -168,7 +170,7 @@ abstract class PBMAC1Core extends HmacCore { java.util.Arrays.fill(passwdChars, ' '); SecretKey s = null; - + PBKDF2Core kdf = getKDFImpl(kdfAlgo); try { s = kdf.engineGenerateSecret(pbeSpec); diff --git a/src/share/classes/com/sun/crypto/provider/SunJCE.java b/src/share/classes/com/sun/crypto/provider/SunJCE.java index 7be5416d390fc0f810402ce46e283dfcc7c36975..a6843ff4f591fd5bc7b9c6edb37d9fd86175effe 100644 --- a/src/share/classes/com/sun/crypto/provider/SunJCE.java +++ b/src/share/classes/com/sun/crypto/provider/SunJCE.java @@ -731,10 +731,11 @@ public final class SunJCE extends Provider { put("Mac.HmacSHA384 SupportedKeyFormats", "RAW"); put("Mac.HmacSHA512 SupportedKeyFormats", "RAW"); put("Mac.HmacPBESHA1 SupportedKeyFormats", "RAW"); - put("Mac.HmacPBESHA224 SupportedKeyFormats", "RAW"); - put("Mac.HmacPBESHA256 SupportedKeyFormats", "RAW"); - put("Mac.HmacPBESHA384 SupportedKeyFormats", "RAW"); - put("Mac.HmacPBESHA512 SupportedKeyFormats", "RAW"); + put("Mac.PBEWithHmacSHA1 SupportedKeyFormatS", "RAW"); + put("Mac.PBEWithHmacSHA224 SupportedKeyFormats", "RAW"); + put("Mac.PBEWithHmacSHA256 SupportedKeyFormats", "RAW"); + put("Mac.PBEWithHmacSHA384 SupportedKeyFormats", "RAW"); + put("Mac.PBEWithHmacSHA512 SupportedKeyFormats", "RAW"); put("Mac.SslMacMD5 SupportedKeyFormats", "RAW"); put("Mac.SslMacSHA1 SupportedKeyFormats", "RAW"); diff --git a/src/share/classes/com/sun/jndi/toolkit/ctx/Continuation.java b/src/share/classes/com/sun/jndi/toolkit/ctx/Continuation.java index 0e1ebe30cb235ea5e5f2331ef7679777f4a7ce1a..6c09f935f97eb929525382b299f5700e013a7745 100644 --- a/src/share/classes/com/sun/jndi/toolkit/ctx/Continuation.java +++ b/src/share/classes/com/sun/jndi/toolkit/ctx/Continuation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -90,14 +90,16 @@ public class Continuation extends ResolveResult { * Constructs a new instance of Continuation. * @param top The name of the object that is to be resolved/operated upon. * This becomes the Continuation's 'starter' and is used to - * calculate the "resolved name" when filling in a NamingException. + * calculate the "resolved name" when filling in a NamingException. * @param environment The environment used by the caller. It is used - * when setting the "environment" of a CannotProceedException. + * when setting the "environment" of a CannotProceedException. */ + @SuppressWarnings("unchecked") // For Hashtable clone: environment.clone() public Continuation(Name top, Hashtable environment) { super(); starter = top; - this.environment = environment; + this.environment = (Hashtable) + ((environment == null) ? null : environment.clone()); } /** diff --git a/src/share/classes/com/sun/jndi/toolkit/dir/LazySearchEnumerationImpl.java b/src/share/classes/com/sun/jndi/toolkit/dir/LazySearchEnumerationImpl.java index 0456301c3e5d7fc31598a47cdf2f9ef65a8558e0..b6efbedfa4b242e1f730ded117aba65a95ebb440 100644 --- a/src/share/classes/com/sun/jndi/toolkit/dir/LazySearchEnumerationImpl.java +++ b/src/share/classes/com/sun/jndi/toolkit/dir/LazySearchEnumerationImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -69,6 +69,7 @@ final public class LazySearchEnumerationImpl } } + @SuppressWarnings("unchecked") // For Hashtable clone: env.clone() public LazySearchEnumerationImpl(NamingEnumeration candidates, AttrFilter filter, SearchControls cons, Context ctx, Hashtable env, boolean useFactory) @@ -76,7 +77,8 @@ final public class LazySearchEnumerationImpl this.candidates = candidates; this.filter = filter; - this.env = env; + this.env = (Hashtable) + ((env == null) ? null : env.clone()); this.context = ctx; this.useFactory = useFactory; diff --git a/src/share/classes/com/sun/jndi/toolkit/dir/SearchFilter.java b/src/share/classes/com/sun/jndi/toolkit/dir/SearchFilter.java index 351e5f26c796ea09597266c9e236869e38910bc1..4ad7a622be82694a727eaf394cdf05aaa5878d1e 100644 --- a/src/share/classes/com/sun/jndi/toolkit/dir/SearchFilter.java +++ b/src/share/classes/com/sun/jndi/toolkit/dir/SearchFilter.java @@ -396,7 +396,7 @@ public class SearchFilter implements AttrFilter { // do we need to begin with the first token? if(proto.charAt(0) != WILDCARD_TOKEN && - !value.toString().toLowerCase(Locale.ENGLISH).startsWith( + !value.toLowerCase(Locale.ENGLISH).startsWith( subStrs.nextToken().toLowerCase(Locale.ENGLISH))) { if(debug) { System.out.println("faild initial test"); diff --git a/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java b/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java index 3d8757b760849a91f4309ea4d9c8add01deff51f..3e90b27bcce9163dbf061569be1ce7606f994103 100644 --- a/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java +++ b/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -53,7 +53,8 @@ abstract public class GenericURLContext implements Context { @SuppressWarnings("unchecked") // Expect Hashtable public GenericURLContext(Hashtable env) { // context that is not tied to any specific URL - myEnv = (Hashtable)env; // copied on write + myEnv = + (Hashtable)(env == null ? null : env.clone()); } public void close() throws NamingException { @@ -488,22 +489,19 @@ abstract public class GenericURLContext implements Context { return result; } - @SuppressWarnings("unchecked") // clone() public Object removeFromEnvironment(String propName) throws NamingException { if (myEnv == null) { return null; } - myEnv = (Hashtable)myEnv.clone(); return myEnv.remove(propName); } - @SuppressWarnings("unchecked") // clone() public Object addToEnvironment(String propName, Object propVal) throws NamingException { - myEnv = (myEnv == null) - ? new Hashtable(11, 0.75f) - : (Hashtable)myEnv.clone(); + if (myEnv == null) { + myEnv = new Hashtable(11, 0.75f); + } return myEnv.put(propName, propVal); } diff --git a/src/share/classes/com/sun/management/DiagnosticCommandMBean.java b/src/share/classes/com/sun/management/DiagnosticCommandMBean.java new file mode 100644 index 0000000000000000000000000000000000000000..da2d510024e70973786fcb4a6b930f193fe86469 --- /dev/null +++ b/src/share/classes/com/sun/management/DiagnosticCommandMBean.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.management; + +import java.lang.management.PlatformManagedObject; +import javax.management.DynamicMBean; + +/** + * Management interface for the diagnostic commands for the HotSpot Virtual Machine. + * + *

The {code DiagnosticCommandMBean} is registered to the + * {@linkplain java.lang.management.ManagementFactory#getPlatformMBeanServer + * platform MBeanServer} as are other platform MBeans. + * + *

The {@link javax.management.ObjectName ObjectName} for uniquely identifying + * the diagnostic MBean within an MBeanServer is: + *

+ * {@code com.sun.management:type=DiagnosticCommand} + *
+ * + *

This MBean is a {@link javax.management.DynamicMBean DynamicMBean} + * and also a {@link javax.management.NotificationEmitter}. + * The {@code DiagnosticCommandMBean} is generated at runtime and is subject to + * modifications during the lifetime of the Java virtual machine. + * + * A diagnostic command is represented as an operation of + * the {@code DiagnosticCommandMBean} interface. Each diagnostic command has: + *

    + *
  • the diagnostic command name which is the name being referenced in + * the HotSpot Virtual Machine
  • + *
  • the MBean operation name which is the + * {@linkplain javax.management.MBeanOperationInfo#getName() name} + * generated for the diagnostic command operation invocation. + * The MBean operation name is implementation dependent
  • + *
+ * + * The recommended way to transform a diagnostic command name into a MBean + * operation name is as follows: + *
    + *
  • All characters from the first one to the first dot are set to be + * lower-case characters
  • + *
  • Every dot or underline character is removed and the following + * character is set to be an upper-case character
  • + *
  • All other characters are copied without modification
  • + *
+ * + *

The diagnostic command name is always provided with the meta-data on the + * operation in a field named {@code dcmd.name} (see below). + * + *

A diagnostic command may or may not support options or arguments. + * All the operations return {@code String} and either take + * no parameter for operations that do not support any option or argument, + * or take a {@code String[]} parameter for operations that support at least + * one option or argument. + * Each option or argument must be stored in a single String. + * Options or arguments split across several String instances are not supported. + * + *

The distinction between options and arguments: options are identified by + * the option name while arguments are identified by their position in the + * command line. Options and arguments are processed in the order of the array + * passed to the invocation method. + * + *

Like any operation of a dynamic MBean, each of these operations is + * described by {@link javax.management.MBeanOperationInfo MBeanOperationInfo} + * instance. Here's the values returned by this object: + *

    + *
  • {@link javax.management.MBeanOperationInfo#getName() getName()} + * returns the operation name generated from the diagnostic command name
  • + *
  • {@link javax.management.MBeanOperationInfo#getDescription() getDescription()} + * returns the diagnostic command description + * (the same as the one return in the 'help' command)
  • + *
  • {@link javax.management.MBeanOperationInfo#getImpact() getImpact()} + * returns ACTION_INFO
  • + *
  • {@link javax.management.MBeanOperationInfo#getReturnType() getReturnType()} + * returns {@code java.lang.String}
  • + *
  • {@link javax.management.MBeanOperationInfo#getDescriptor() getDescriptor()} + * returns a Descriptor instance (see below)
  • + *
+ * + *

The {@link javax.management.Descriptor Descriptor} + * is a collection of fields containing additional + * meta-data for a JMX element. A field is a name and an associated value. + * The additional meta-data provided for an operation associated with a + * diagnostic command are described in the table below: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameTypeDescription
dcmd.nameStringThe original diagnostic command name (not the operation name)
dcmd.descriptionStringThe diagnostic command description
dcmd.helpStringThe full help message for this diagnostic command (same output as + * the one produced by the 'help' command)
dcmd.vmImpactStringThe impact of the diagnostic command, + * this value is the same as the one printed in the 'impact' + * section of the help message of the diagnostic command, and it + * is different from the getImpact() of the MBeanOperationInfo
dcmd.enabledbooleanTrue if the diagnostic command is enabled, false otherwise
dcmd.permissionClassStringSome diagnostic command might require a specific permission to be + * executed, in addition to the MBeanPermission to invoke their + * associated MBean operation. This field returns the fully qualified + * name of the permission class or null if no permission is required + *
dcmd.permissionNameStringThe fist argument of the permission required to execute this + * diagnostic command or null if no permission is required
dcmd.permissionActionStringThe second argument of the permission required to execute this + * diagnostic command or null if the permission constructor has only + * one argument (like the ManagementPermission) or if no permission + * is required
dcmd.argumentsDescriptorA Descriptor instance containing the descriptions of options and + * arguments supported by the diagnostic command (see below)
+ *

+ * + *

The description of parameters (options or arguments) of a diagnostic + * command is provided within a Descriptor instance. In this Descriptor, + * each field name is a parameter name, and each field value is itself + * a Descriptor instance. The fields provided in this second Descriptor + * instance are described in the table below: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameTypeDescription
dcmd.arg.nameStringThe name of the parameter
dcmd.arg.typeStringThe type of the parameter. The returned String is the name of a type + * recognized by the diagnostic command parser. These types are not + * Java types and are implementation dependent. + *
dcmd.arg.descriptionStringThe parameter description
dcmd.arg.isMandatorybooleanTrue if the parameter is mandatory, false otherwise
dcmd.arg.isOptionbooleanTrue if the parameter is an option, false if it is an argument
dcmd.arg.isMultiplebooleanTrue if the parameter can be specified several times, false + * otherwise
+ * + *

When the set of diagnostic commands currently supported by the Java + * Virtual Machine is modified, the {@code DiagnosticCommandMBean} emits + * a {@link javax.management.Notification} with a + * {@linkplain javax.management.Notification#getType() type} of + * + * {@code "jmx.mbean.info.changed"} and a + * {@linkplain javax.management.Notification#getUserData() userData} that + * is the new {@code MBeanInfo}. + * + * @since 8 + */ +public interface DiagnosticCommandMBean extends DynamicMBean +{ + +} diff --git a/src/share/classes/java/io/FileInputStream.java b/src/share/classes/java/io/FileInputStream.java index 740b6744047a50e27c7fbc40061610f762ec0575..90d1ad5cc3ec2fdab55866385ba1ec86e09bd040 100644 --- a/src/share/classes/java/io/FileInputStream.java +++ b/src/share/classes/java/io/FileInputStream.java @@ -240,13 +240,15 @@ class FileInputStream extends InputStream * *

The skip method may, for a variety of * reasons, end up skipping over some smaller number of bytes, - * possibly 0. If n is negative, an - * IOException is thrown, even though the skip - * method of the {@link InputStream} superclass does nothing in this case. - * The actual number of bytes skipped is returned. + * possibly 0. If n is negative, the method + * will try to skip backwards. In case the backing file does not support + * backward skip at its current position, an IOException is + * thrown. The actual number of bytes skipped is returned. If it skips + * forwards, it returns a positive value. If it skips backwards, it + * returns a negative value. * - *

This method may skip more bytes than are remaining in the backing - * file. This produces no exception and the number of bytes skipped + *

This method may skip more bytes than what are remaining in the + * backing file. This produces no exception and the number of bytes skipped * may include some number of bytes that were beyond the EOF of the * backing file. Attempting to read from the stream after skipping past * the end will result in -1 indicating the end of the file. @@ -261,9 +263,10 @@ class FileInputStream extends InputStream /** * Returns an estimate of the number of remaining bytes that can be read (or * skipped over) from this input stream without blocking by the next - * invocation of a method for this input stream. The next invocation might be - * the same thread or another thread. A single read or skip of this - * many bytes will not block, but may read or skip fewer bytes. + * invocation of a method for this input stream. Returns 0 when the file + * position is beyond EOF. The next invocation might be the same thread + * or another thread. A single read or skip of this many bytes will not + * block, but may read or skip fewer bytes. * *

In some cases, a non-blocking read (or skip) may appear to be * blocked when it is merely slow, for example when reading large diff --git a/src/share/classes/java/io/InputStream.java b/src/share/classes/java/io/InputStream.java index cb61314b01db29eada561a1c938e28f25617c758..3c7ff17f60900f71b03529bc5c38ce86aca17650 100644 --- a/src/share/classes/java/io/InputStream.java +++ b/src/share/classes/java/io/InputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -193,8 +193,10 @@ public abstract class InputStream implements Closeable { * up skipping over some smaller number of bytes, possibly 0. * This may result from any of a number of conditions; reaching end of file * before n bytes have been skipped is only one possibility. - * The actual number of bytes skipped is returned. If n is - * negative, no bytes are skipped. + * The actual number of bytes skipped is returned. If {@code n} is + * negative, the {@code skip} method for class {@code InputStream} always + * returns 0, and no bytes are skipped. Subclasses may handle the negative + * value differently. * *

The skip method of this class creates a * byte array and then repeatedly reads into it until n bytes diff --git a/src/share/classes/java/lang/AbstractStringBuilder.java b/src/share/classes/java/lang/AbstractStringBuilder.java index c319fa4da6e0b2e6419ad9c88638c25861ebe13c..a5bde616f8a25d6e41b467b873b9e2b0fc64df34 100644 --- a/src/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/share/classes/java/lang/AbstractStringBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -689,7 +689,7 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence { * @return a reference to this object. */ public AbstractStringBuilder append(float f) { - new FloatingDecimal(f).appendTo(this); + FloatingDecimal.appendTo(f,this); return this; } @@ -706,7 +706,7 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence { * @return a reference to this object. */ public AbstractStringBuilder append(double d) { - new FloatingDecimal(d).appendTo(this); + FloatingDecimal.appendTo(d,this); return this; } diff --git a/src/share/classes/java/lang/CharSequence.java b/src/share/classes/java/lang/CharSequence.java index d34c1e02e276eb0bb84e1e1b0538e71a2cd867f4..30a235908755a3d318f94a07eb6f3218184fea8c 100644 --- a/src/share/classes/java/lang/CharSequence.java +++ b/src/share/classes/java/lang/CharSequence.java @@ -179,10 +179,25 @@ public interface CharSequence { @Override public void forEachRemaining(IntConsumer block) { - while (cur < length()) { - int cp = Character.codePointAt(CharSequence.this, cur); - cur += Character.charCount(cp); - block.accept(cp); + final int length = length(); + int i = cur; + try { + while (i < length) { + char c1 = charAt(i++); + if (!Character.isHighSurrogate(c1) || i >= length) { + block.accept(c1); + } else { + char c2 = charAt(i); + if (Character.isLowSurrogate(c2)) { + i++; + block.accept(Character.toCodePoint(c1, c2)); + } else { + block.accept(c1); + } + } + } + } finally { + cur = i; } } @@ -191,12 +206,20 @@ public interface CharSequence { } public int nextInt() { - if (!hasNext()) { + final int length = length(); + + if (cur >= length) { throw new NoSuchElementException(); } - int cp = Character.codePointAt(CharSequence.this, cur); - cur += Character.charCount(cp); - return cp; + char c1 = charAt(cur++); + if (Character.isHighSurrogate(c1) && cur < length) { + char c2 = charAt(cur); + if (Character.isLowSurrogate(c2)) { + cur++; + return Character.toCodePoint(c1, c2); + } + } + return c1; } } diff --git a/src/share/classes/java/lang/Class.java b/src/share/classes/java/lang/Class.java index 365c8aadb7267d52a79b1865f8ff3b567590302c..906b0fdce08df51eb21585dac08c9112dcee8f11 100644 --- a/src/share/classes/java/lang/Class.java +++ b/src/share/classes/java/lang/Class.java @@ -28,6 +28,7 @@ package java.lang; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; import java.lang.reflect.Member; import java.lang.reflect.Field; import java.lang.reflect.Executable; @@ -115,9 +116,9 @@ import sun.reflect.misc.ReflectUtil; * @since JDK1.0 */ public final class Class implements java.io.Serializable, - java.lang.reflect.GenericDeclaration, - java.lang.reflect.Type, - java.lang.reflect.AnnotatedElement { + GenericDeclaration, + Type, + AnnotatedElement { private static final int ANNOTATION= 0x00002000; private static final int ENUM = 0x00004000; private static final int SYNTHETIC = 0x00001000; @@ -2284,14 +2285,6 @@ public final class Class implements java.io.Serializable, */ private native java.security.ProtectionDomain getProtectionDomain0(); - - /** - * Set the ProtectionDomain for this class. Called by - * ClassLoader.defineClass. - */ - native void setProtectionDomain0(java.security.ProtectionDomain pd); - - /* * Return the Virtual Machine's Class object for the named * primitive type. @@ -3255,7 +3248,7 @@ public final class Class implements java.io.Serializable, */ @Override public boolean isAnnotationPresent(Class annotationClass) { - return AnnotatedElement.super.isAnnotationPresent(annotationClass); + return GenericDeclaration.super.isAnnotationPresent(annotationClass); } /** diff --git a/src/share/classes/java/lang/Double.java b/src/share/classes/java/lang/Double.java index 9bdb0ca46794baf5c4e19979f37965d2bbfbf2ce..a32d04cf75fe04f5d13c715a74543fecd67cc067 100644 --- a/src/share/classes/java/lang/Double.java +++ b/src/share/classes/java/lang/Double.java @@ -201,7 +201,7 @@ public final class Double extends Number implements Comparable { * @return a string representation of the argument. */ public static String toString(double d) { - return new FloatingDecimal(d).toJavaFormatString(); + return FloatingDecimal.toJavaFormatString(d); } /** @@ -509,7 +509,7 @@ public final class Double extends Number implements Comparable { * parsable number. */ public static Double valueOf(String s) throws NumberFormatException { - return new Double(FloatingDecimal.readJavaFormatString(s).doubleValue()); + return new Double(parseDouble(s)); } /** @@ -545,7 +545,7 @@ public final class Double extends Number implements Comparable { * @since 1.2 */ public static double parseDouble(String s) throws NumberFormatException { - return FloatingDecimal.readJavaFormatString(s).doubleValue(); + return FloatingDecimal.parseDouble(s); } /** diff --git a/src/share/classes/java/lang/Float.java b/src/share/classes/java/lang/Float.java index bdb8f63fbf1a27cf5b2c530860b43a685ef80506..5b077111fe04ea5177c051055c13cbb7a1729072 100644 --- a/src/share/classes/java/lang/Float.java +++ b/src/share/classes/java/lang/Float.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -203,7 +203,7 @@ public final class Float extends Number implements Comparable { * @return a string representation of the argument. */ public static String toString(float f) { - return new FloatingDecimal(f).toJavaFormatString(); + return FloatingDecimal.toJavaFormatString(f); } /** @@ -421,7 +421,7 @@ public final class Float extends Number implements Comparable { * parsable number. */ public static Float valueOf(String s) throws NumberFormatException { - return new Float(FloatingDecimal.readJavaFormatString(s).floatValue()); + return new Float(parseFloat(s)); } /** @@ -456,7 +456,7 @@ public final class Float extends Number implements Comparable { * @since 1.2 */ public static float parseFloat(String s) throws NumberFormatException { - return FloatingDecimal.readJavaFormatString(s).floatValue(); + return FloatingDecimal.parseFloat(s); } /** diff --git a/src/share/classes/java/lang/Integer.java b/src/share/classes/java/lang/Integer.java index a0eeecf2d986e76f9abc7cbfc63b93ae4c33eb79..e5de967a0699f353c3943c6195b34a53bed4b5c9 100644 --- a/src/share/classes/java/lang/Integer.java +++ b/src/share/classes/java/lang/Integer.java @@ -26,7 +26,6 @@ package java.lang; import java.lang.annotation.Native; -import java.util.Properties; /** * The {@code Integer} class wraps a value of the primitive type @@ -185,7 +184,7 @@ public final class Integer extends Number implements Comparable { * @since 1.8 */ public static String toUnsignedString(int i, int radix) { - return Long.toString(toUnsignedLong(i), radix); + return Long.toUnsignedString(toUnsignedLong(i), radix); } /** @@ -307,20 +306,39 @@ public final class Integer extends Number implements Comparable { /** * Convert the integer to an unsigned number. */ - private static String toUnsignedString0(int i, int shift) { - char[] buf = new char[32]; - int charPos = 32; + private static String toUnsignedString0(int val, int shift) { + // assert shift > 0 && shift <=5 : "Illegal shift value"; + int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val); + int chars = Math.max(((mag + (shift - 1)) / shift), 1); + char[] buf = new char[chars]; + + formatUnsignedInt(val, shift, buf, 0, chars); + + // Use special constructor which takes over "buf". + return new String(buf, true); + } + + /** + * Format a long (treated as unsigned) into a character buffer. + * @param val the unsigned int to format + * @param shift the log2 of the base to format in (4 for hex, 3 for octal, 1 for binary) + * @param buf the character buffer to write to + * @param offset the offset in the destination buffer to start at + * @param len the number of characters to write + * @return the lowest character location used + */ + static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) { + int charPos = len; int radix = 1 << shift; int mask = radix - 1; do { - buf[--charPos] = digits[i & mask]; - i >>>= shift; - } while (i != 0); + buf[offset + --charPos] = Integer.digits[val & mask]; + val >>>= shift; + } while (val != 0 && charPos > 0); - return new String(buf, charPos, (32 - charPos)); + return charPos; } - final static char [] DigitTens = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', @@ -875,6 +893,7 @@ public final class Integer extends Number implements Comparable { * Returns the value of this {@code Integer} as a {@code long} * after a widening primitive conversion. * @jls 5.1.2 Widening Primitive Conversions + * @see Integer#toUnsignedLong(int) */ public long longValue() { return (long)value; diff --git a/src/share/classes/java/lang/Long.java b/src/share/classes/java/lang/Long.java index 76dbfc38a7b0ea85cf3e90b58b81e2f481718a9c..80967aca5de67b6b971eb54166af3711b73977e0 100644 --- a/src/share/classes/java/lang/Long.java +++ b/src/share/classes/java/lang/Long.java @@ -28,6 +28,7 @@ package java.lang; import java.lang.annotation.Native; import java.math.*; + /** * The {@code Long} class wraps a value of the primitive type {@code * long} in an object. An object of type {@code Long} contains a @@ -344,18 +345,39 @@ public final class Long extends Number implements Comparable { } /** - * Convert the integer to an unsigned number. + * Format a long (treated as unsigned) into a String. + * @param val the value to format + * @param shift the log2 of the base to format in (4 for hex, 3 for octal, 1 for binary) */ - private static String toUnsignedString0(long i, int shift) { - char[] buf = new char[64]; - int charPos = 64; + static String toUnsignedString0(long val, int shift) { + // assert shift > 0 && shift <=5 : "Illegal shift value"; + int mag = Long.SIZE - Long.numberOfLeadingZeros(val); + int chars = Math.max(((mag + (shift - 1)) / shift), 1); + char[] buf = new char[chars]; + + formatUnsignedLong(val, shift, buf, 0, chars); + return new String(buf, true); + } + + /** + * Format a long (treated as unsigned) into a character buffer. + * @param val the unsigned long to format + * @param shift the log2 of the base to format in (4 for hex, 3 for octal, 1 for binary) + * @param buf the character buffer to write to + * @param offset the offset in the destination buffer to start at + * @param len the number of characters to write + * @return the lowest character location used + */ + static int formatUnsignedLong(long val, int shift, char[] buf, int offset, int len) { + int charPos = len; int radix = 1 << shift; - long mask = radix - 1; + int mask = radix - 1; do { - buf[--charPos] = Integer.digits[(int)(i & mask)]; - i >>>= shift; - } while (i != 0); - return new String(buf, charPos, (64 - charPos)); + buf[offset + --charPos] = Integer.digits[((int) val) & mask]; + val >>>= shift; + } while (val != 0 && charPos > 0); + + return charPos; } /** diff --git a/src/share/classes/java/lang/String.java b/src/share/classes/java/lang/String.java index c738a9eb619d148bc8cdab85c4c22c11177458ce..748c8a4bbefb42094b8d69b015bb030704e9eea8 100644 --- a/src/share/classes/java/lang/String.java +++ b/src/share/classes/java/lang/String.java @@ -1010,13 +1010,14 @@ public final class String private boolean nonSyncContentEquals(AbstractStringBuilder sb) { char v1[] = value; char v2[] = sb.getValue(); - int i = 0; - int n = value.length; - while (n-- != 0) { + int n = v1.length; + if (n != sb.length()) { + return false; + } + for (int i = 0; i < n; i++) { if (v1[i] != v2[i]) { return false; } - i++; } return true; } @@ -1038,8 +1039,6 @@ public final class String * @since 1.5 */ public boolean contentEquals(CharSequence cs) { - if (value.length != cs.length()) - return false; // Argument is a StringBuffer, StringBuilder if (cs instanceof AbstractStringBuilder) { if (cs instanceof StringBuffer) { @@ -1055,12 +1054,14 @@ public final class String return true; // Argument is a generic CharSequence char v1[] = value; - int i = 0; - int n = value.length; - while (n-- != 0) { - if (v1[i] != cs.charAt(i)) + int n = v1.length; + if (n != cs.length()) { + return false; + } + for (int i = 0; i < n; i++) { + if (v1[i] != cs.charAt(i)) { return false; - i++; + } } return true; } diff --git a/src/share/classes/java/lang/StringBuffer.java b/src/share/classes/java/lang/StringBuffer.java index 8ead0721ffd8edacc67ce66edfbab2371f802153..9bc8f3ba10fd7b55f4d704cc2179ea8cb1e57e07 100644 --- a/src/share/classes/java/lang/StringBuffer.java +++ b/src/share/classes/java/lang/StringBuffer.java @@ -335,10 +335,8 @@ import java.util.Arrays; * @since 1.5 */ @Override - public StringBuffer append(CharSequence s) { - // Note, synchronization achieved via invocations of other StringBuffer methods after - // narrowing of s to specific type - // Ditto for toStringCache clearing + public synchronized StringBuffer append(CharSequence s) { + toStringCache = null; super.append(s); return this; } diff --git a/src/share/classes/java/lang/Thread.java b/src/share/classes/java/lang/Thread.java index e418f1f9a937bcb7269a564e55afb23683c4d7d4..81bebc58b217de346d0fae4e851fe1caffe62d9f 100644 --- a/src/share/classes/java/lang/Thread.java +++ b/src/share/classes/java/lang/Thread.java @@ -145,10 +145,10 @@ class Thread implements Runnable { registerNatives(); } - private char name[]; - private int priority; - private Thread threadQ; - private long eetop; + private volatile char name[]; + private int priority; + private Thread threadQ; + private long eetop; /* Whether or not to single_step this thread. */ private boolean single_step; @@ -1135,7 +1135,7 @@ class Thread implements Runnable { * @see #getName * @see #checkAccess() */ - public final void setName(String name) { + public final synchronized void setName(String name) { checkAccess(); this.name = name.toCharArray(); if (threadStatus != 0) { @@ -1150,7 +1150,7 @@ class Thread implements Runnable { * @see #setName(String) */ public final String getName() { - return String.valueOf(name); + return new String(name, true); } /** diff --git a/src/share/classes/java/lang/annotation/IncompleteAnnotationException.java b/src/share/classes/java/lang/annotation/IncompleteAnnotationException.java index dcd4f2a34c5686d6378e3660522b0ebbe33a7e02..1f11b72bc1524e61f4247112016fafac3f9969fe 100644 --- a/src/share/classes/java/lang/annotation/IncompleteAnnotationException.java +++ b/src/share/classes/java/lang/annotation/IncompleteAnnotationException.java @@ -55,8 +55,7 @@ public class IncompleteAnnotationException extends RuntimeException { public IncompleteAnnotationException( Class annotationType, String elementName) { - super(annotationType.getName().toString() + - " missing element " + + super(annotationType.getName() + " missing element " + elementName.toString()); this.annotationType = annotationType; diff --git a/src/share/classes/java/lang/management/ManagementFactory.java b/src/share/classes/java/lang/management/ManagementFactory.java index 278ace1d7d11cb03f0f97a44ef0bd9590488f4e3..384cf4cda069f38efa94e1b744531f0da14fcba0 100644 --- a/src/share/classes/java/lang/management/ManagementFactory.java +++ b/src/share/classes/java/lang/management/ManagementFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,7 +42,9 @@ import javax.management.StandardMBean; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.security.AccessController; import java.security.Permission; import java.security.PrivilegedAction; @@ -482,6 +484,11 @@ public class ManagementFactory { } } } + HashMap dynmbeans = + ManagementFactoryHelper.getPlatformDynamicMBeans(); + for (Map.Entry e : dynmbeans.entrySet()) { + addDynamicMBean(platformMBeanServer, e.getValue(), e.getKey()); + } } return platformMBeanServer; } @@ -825,4 +832,24 @@ public class ManagementFactory { } } + /** + * Registers a DynamicMBean. + */ + private static void addDynamicMBean(final MBeanServer mbs, + final DynamicMBean dmbean, + final ObjectName on) { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Void run() throws InstanceAlreadyExistsException, + MBeanRegistrationException, + NotCompliantMBeanException { + mbs.registerMBean(dmbean, on); + return null; + } + }); + } catch (PrivilegedActionException e) { + throw new RuntimeException(e.getException()); + } + } } diff --git a/src/share/classes/java/lang/ref/Reference.java b/src/share/classes/java/lang/ref/Reference.java index 3d535776c60c7d48d9f3ef62358a91b74c8eaeab..bc24e9df02677b3ddf9766f692ba2a2232b6bb60 100644 --- a/src/share/classes/java/lang/ref/Reference.java +++ b/src/share/classes/java/lang/ref/Reference.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -138,8 +138,23 @@ public abstract class Reference { pending = r.discovered; r.discovered = null; } else { + // The waiting on the lock may cause an OOME because it may try to allocate + // exception objects, so also catch OOME here to avoid silent exit of the + // reference handler thread. + // + // Explicitly define the order of the two exceptions we catch here + // when waiting for the lock. + // + // We do not want to try to potentially load the InterruptedException class + // (which would be done if this was its first use, and InterruptedException + // were checked first) in this situation. + // + // This may lead to the VM not ever trying to load the InterruptedException + // class again. try { - lock.wait(); + try { + lock.wait(); + } catch (OutOfMemoryError x) { } } catch (InterruptedException x) { } continue; } diff --git a/src/share/classes/java/lang/reflect/GenericDeclaration.java b/src/share/classes/java/lang/reflect/GenericDeclaration.java index 4f06c621f26ffff5010f3361c7b756151cfcefe0..042e359ad6f7dca430a7eb1eb1d780e336e02738 100644 --- a/src/share/classes/java/lang/reflect/GenericDeclaration.java +++ b/src/share/classes/java/lang/reflect/GenericDeclaration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +30,7 @@ package java.lang.reflect; * * @since 1.5 */ -public interface GenericDeclaration { +public interface GenericDeclaration extends AnnotatedElement { /** * Returns an array of {@code TypeVariable} objects that * represent the type variables declared by the generic diff --git a/src/share/classes/java/net/CookiePolicy.java b/src/share/classes/java/net/CookiePolicy.java index 63f07157bf7647ad8544e81a1fc4142f475dd31c..aae5c1a80536688fbd57532c5bfe825f5a75a052 100644 --- a/src/share/classes/java/net/CookiePolicy.java +++ b/src/share/classes/java/net/CookiePolicy.java @@ -59,6 +59,8 @@ public interface CookiePolicy { */ public static final CookiePolicy ACCEPT_ORIGINAL_SERVER = new CookiePolicy(){ public boolean shouldAccept(URI uri, HttpCookie cookie) { + if (uri == null || cookie == null) + return false; return HttpCookie.domainMatches(cookie.getDomain(), uri.getHost()); } }; diff --git a/src/share/classes/java/net/HttpCookie.java b/src/share/classes/java/net/HttpCookie.java index d5a36df507f890a3713212c0a769da63913dd1e3..d265e284c267ead981ee631fcc7c9dd4101b1f62 100644 --- a/src/share/classes/java/net/HttpCookie.java +++ b/src/share/classes/java/net/HttpCookie.java @@ -128,8 +128,7 @@ public final class HttpCookie implements Cloneable { * a {@code String} specifying the value of the cookie * * @throws IllegalArgumentException - * if the cookie name contains illegal characters or it is one of - * the tokens reserved for use by the cookie protocol + * if the cookie name contains illegal characters * @throws NullPointerException * if {@code name} is {@code null} * @@ -142,7 +141,7 @@ public final class HttpCookie implements Cloneable { private HttpCookie(String name, String value, String header) { name = name.trim(); - if (name.length() == 0 || !isToken(name)) { + if (name.length() == 0 || !isToken(name) || name.charAt(0) == '$') { throw new IllegalArgumentException("Illegal cookie name"); } @@ -170,9 +169,8 @@ public final class HttpCookie implements Cloneable { * @return a List of cookie parsed from header line string * * @throws IllegalArgumentException - * if header string violates the cookie specification's syntax, or - * the cookie name contains illegal characters, or the cookie name - * is one of the tokens reserved for use by the cookie protocol + * if header string violates the cookie specification's syntax or + * the cookie name contains illegal characters. * @throws NullPointerException * if the header string is {@code null} */ diff --git a/src/share/classes/java/net/HttpURLPermission.java b/src/share/classes/java/net/HttpURLPermission.java index 52d6e79344a776ad15b70a0b738e3ba29dd266c3..55d37fda8ca2d7a54e0370d8eda697dbf5fb672d 100644 --- a/src/share/classes/java/net/HttpURLPermission.java +++ b/src/share/classes/java/net/HttpURLPermission.java @@ -377,7 +377,7 @@ public final class HttpURLPermission extends Permission { throw new IllegalArgumentException ("unexpected URL scheme"); } if (!u.getSchemeSpecificPart().equals("*")) { - u = URI.create(scheme + "://" + u.getAuthority() + u.getPath()); + u = URI.create(scheme + "://" + u.getRawAuthority() + u.getRawPath()); } return u; } diff --git a/src/share/classes/java/nio/Buffer.java b/src/share/classes/java/nio/Buffer.java index 1c0591fb28f98dc05b6467250037e1f39cc0cfd9..24d3cf8c56ab678a33e255ab3fd849f183a10adf 100644 --- a/src/share/classes/java/nio/Buffer.java +++ b/src/share/classes/java/nio/Buffer.java @@ -25,6 +25,7 @@ package java.nio; +import java.util.Spliterator; /** * A container for data of a specific primitive type. @@ -173,6 +174,13 @@ package java.nio; public abstract class Buffer { + /** + * The characteristics of Spliterators that traverse and split elements + * maintained in Buffers. + */ + static final int SPLITERATOR_CHARACTERISTICS = + Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED; + // Invariants: mark <= position <= limit <= capacity private int mark = -1; private int position = 0; diff --git a/src/share/classes/java/nio/ByteBufferAs-X-Buffer.java.template b/src/share/classes/java/nio/ByteBufferAs-X-Buffer.java.template index 1673da2bf45c349b74b872cb5c1f9045c875d161..d5be9669fb8e27ae68ce03ac37d1a75e0727e2f1 100644 --- a/src/share/classes/java/nio/ByteBufferAs-X-Buffer.java.template +++ b/src/share/classes/java/nio/ByteBufferAs-X-Buffer.java.template @@ -115,6 +115,12 @@ class ByteBufferAs$Type$Buffer$RW$$BO$ // package-private return Bits.get$Type$$BO$(bb, ix(checkIndex(i))); } +#if[streamableType] + $type$ getUnchecked(int i) { + return Bits.get$Type$$BO$(bb, ix(i)); + } +#end[streamableType] + #end[rw] public $Type$Buffer put($type$ x) { diff --git a/src/share/classes/java/nio/CharBufferSpliterator.java b/src/share/classes/java/nio/CharBufferSpliterator.java new file mode 100644 index 0000000000000000000000000000000000000000..19fd8a8f0ba8b858239f107d56769fc12b535b07 --- /dev/null +++ b/src/share/classes/java/nio/CharBufferSpliterator.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this +* particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.nio; + +import java.util.Comparator; +import java.util.Spliterator; +import java.util.function.IntConsumer; + +/** + * A Spliterator.OfInt for sources that traverse and split elements + * maintained in a CharBuffer. + * + * @implNote + * The implementation is based on the code for the Array-based spliterators. + */ +class CharBufferSpliterator implements Spliterator.OfInt { + private final CharBuffer buffer; + private int index; // current index, modified on advance/split + private final int limit; + + CharBufferSpliterator(CharBuffer buffer) { + this(buffer, buffer.position(), buffer.limit()); + } + + CharBufferSpliterator(CharBuffer buffer, int origin, int limit) { + assert origin <= limit; + this.buffer = buffer; + this.index = (origin <= limit) ? origin : limit; + this.limit = limit; + } + + @Override + public OfInt trySplit() { + int lo = index, mid = (lo + limit) >>> 1; + return (lo >= mid) + ? null + : new CharBufferSpliterator(buffer, lo, index = mid); + } + + @Override + public void forEachRemaining(IntConsumer action) { + if (action == null) + throw new NullPointerException(); + CharBuffer cb = buffer; + int i = index; + int hi = limit; + index = hi; + while (i < hi) { + action.accept(cb.getUnchecked(i++)); + } + } + + @Override + public boolean tryAdvance(IntConsumer action) { + if (action == null) + throw new NullPointerException(); + if (index >= 0 && index < limit) { + action.accept(buffer.getUnchecked(index++)); + return true; + } + return false; + } + + @Override + public long estimateSize() { + return (long)(limit - index); + } + + @Override + public int characteristics() { + return Buffer.SPLITERATOR_CHARACTERISTICS; + } +} diff --git a/src/share/classes/java/nio/Direct-X-Buffer.java.template b/src/share/classes/java/nio/Direct-X-Buffer.java.template index d01e873697d658b499629e293c1fb60a354421d3..0523e2a2cd68cffad99ae3fc450181d4c2ba08a9 100644 --- a/src/share/classes/java/nio/Direct-X-Buffer.java.template +++ b/src/share/classes/java/nio/Direct-X-Buffer.java.template @@ -253,6 +253,12 @@ class Direct$Type$Buffer$RW$$BO$ return $fromBits$($swap$(unsafe.get$Swaptype$(ix(checkIndex(i))))); } +#if[streamableType] + $type$ getUnchecked(int i) { + return $fromBits$($swap$(unsafe.get$Swaptype$(ix(i)))); + } +#end[streamableType] + public $Type$Buffer get($type$[] dst, int offset, int length) { #if[rw] if ((length << $LG_BYTES_PER_VALUE$) > Bits.JNI_COPY_TO_ARRAY_THRESHOLD) { diff --git a/src/share/classes/java/nio/Heap-X-Buffer.java.template b/src/share/classes/java/nio/Heap-X-Buffer.java.template index cecc1bacf9a869eec0f19cd226258cebcd66d162..54f7164b24cb1494f331362acb447c56a8c8e4ee 100644 --- a/src/share/classes/java/nio/Heap-X-Buffer.java.template +++ b/src/share/classes/java/nio/Heap-X-Buffer.java.template @@ -139,6 +139,12 @@ class Heap$Type$Buffer$RW$ return hb[ix(checkIndex(i))]; } +#if[streamableType] + $type$ getUnchecked(int i) { + return hb[ix(i)]; + } +#end[streamableType] + public $Type$Buffer get($type$[] dst, int offset, int length) { checkBounds(offset, length, dst.length); if (length > remaining()) diff --git a/src/share/classes/java/nio/StringCharBuffer.java b/src/share/classes/java/nio/StringCharBuffer.java index 47b6bae32289e4b46b66c19e7fc76a2a944b44f8..a277ef559bda7d696e1744bb956481540a690f1c 100644 --- a/src/share/classes/java/nio/StringCharBuffer.java +++ b/src/share/classes/java/nio/StringCharBuffer.java @@ -77,6 +77,10 @@ class StringCharBuffer // package-private return str.charAt(checkIndex(index) + offset); } + char getUnchecked(int index) { + return str.charAt(index + offset); + } + // ## Override bulk get methods for better performance public final CharBuffer put(char c) { diff --git a/src/share/classes/java/nio/X-Buffer.java.template b/src/share/classes/java/nio/X-Buffer.java.template index a727b000bf1ff08a1469d26a6f751cbc94ab9e25..475818d300db8fc74e50255b3ef6f716fe9695f8 100644 --- a/src/share/classes/java/nio/X-Buffer.java.template +++ b/src/share/classes/java/nio/X-Buffer.java.template @@ -30,6 +30,11 @@ package java.nio; #if[char] import java.io.IOException; #end[char] +#if[streamableType] +import java.util.Spliterator; +import java.util.stream.StreamSupport; +import java.util.stream.$Streamtype$Stream; +#end[streamableType] /** * $A$ $type$ buffer. @@ -589,6 +594,19 @@ public abstract class $Type$Buffer */ public abstract $type$ get(int index); +#if[streamableType] + /** + * Absolute get method. Reads the $type$ at the given + * index without any validation of the index. + * + * @param index + * The index from which the $type$ will be read + * + * @return The $type$ at the given index + */ + abstract $type$ getUnchecked(int index); // package-private +#end[streamableType] + /** * Absolute put method  (optional operation). * @@ -1458,4 +1476,16 @@ public abstract class $Type$Buffer #end[byte] +#if[streamableType] + +#if[char] + @Override +#end[char] + public $Streamtype$Stream $type$s() { + return StreamSupport.$streamtype$Stream(() -> new $Type$BufferSpliterator(this), + Buffer.SPLITERATOR_CHARACTERISTICS); + } + +#end[streamableType] + } diff --git a/src/share/classes/java/nio/charset/Charset-X-Coder.java.template b/src/share/classes/java/nio/charset/Charset-X-Coder.java.template index 5b6ecfa63cf8a0c34e9f0c851ec8fba81d1458dd..2449d56cf44c7e7923c8e80a856495474282a2a1 100644 --- a/src/share/classes/java/nio/charset/Charset-X-Coder.java.template +++ b/src/share/classes/java/nio/charset/Charset-X-Coder.java.template @@ -34,6 +34,7 @@ import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.lang.ref.WeakReference; import java.nio.charset.CoderMalfunctionError; // javadoc +import java.util.Arrays; /** @@ -244,7 +245,12 @@ public abstract class Charset$Coder$ { * which is never null and is never empty */ public final $replType$ replacement() { +#if[decoder] return replacement; +#end[decoder] +#if[encoder] + return Arrays.copyOf(replacement, replacement.$replLength$); +#end[encoder] } /** @@ -280,12 +286,15 @@ public abstract class Charset$Coder$ { throw new IllegalArgumentException("Empty replacement"); if (len > max$ItypesPerOtype$) throw new IllegalArgumentException("Replacement too long"); +#if[decoder] + this.replacement = newReplacement; +#end[decoder] #if[encoder] if (!isLegalReplacement(newReplacement)) throw new IllegalArgumentException("Illegal replacement"); + this.replacement = Arrays.copyOf(newReplacement, newReplacement.$replLength$); #end[encoder] - this.replacement = newReplacement; - implReplaceWith(newReplacement); + implReplaceWith(this.replacement); return this; } diff --git a/src/share/classes/java/nio/file/FileTreeIterator.java b/src/share/classes/java/nio/file/FileTreeIterator.java new file mode 100644 index 0000000000000000000000000000000000000000..63b4dc767d842835ee72b4eafa3fc678ed71e554 --- /dev/null +++ b/src/share/classes/java/nio/file/FileTreeIterator.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.nio.file; + +import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.nio.file.FileTreeWalker.Event; + +/** + * An {@code Iterator to iterate over the nodes of a file tree. + * + *

{@code
+ *     try (FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options)) {
+ *         while (iterator.hasNext()) {
+ *             Event ev = iterator.next();
+ *             Path path = ev.file();
+ *             BasicFileAttributes attrs = ev.attributes();
+ *         }
+ *     }
+ * }
+ */ + +class FileTreeIterator implements Iterator, Closeable { + private final FileTreeWalker walker; + private Event next; + + /** + * Creates a new iterator to walk the file tree starting at the given file. + * + * @throws IllegalArgumentException + * if {@code maxDepth} is negative + * @throws IOException + * if an I/O errors occurs opening the starting file + * @throws SecurityException + * if the security manager denies access to the starting file + * @throws NullPointerException + * if {@code start} or {@code options} is {@ocde null} or + * the options array contains a {@code null} element + */ + FileTreeIterator(Path start, int maxDepth, FileVisitOption... options) + throws IOException + { + this.walker = new FileTreeWalker(Arrays.asList(options), maxDepth); + this.next = walker.walk(start); + assert next.type() == FileTreeWalker.EventType.ENTRY || + next.type() == FileTreeWalker.EventType.START_DIRECTORY; + + // IOException if there a problem accessing the starting file + IOException ioe = next.ioeException(); + if (ioe != null) + throw ioe; + } + + private void fetchNextIfNeeded() { + if (next == null) { + FileTreeWalker.Event ev = walker.next(); + while (ev != null) { + IOException ioe = ev.ioeException(); + if (ioe != null) + throw new UncheckedIOException(ioe); + + // END_DIRECTORY events are ignored + if (ev.type() != FileTreeWalker.EventType.END_DIRECTORY) { + next = ev; + return; + } + ev = walker.next(); + } + } + } + + @Override + public boolean hasNext() { + if (!walker.isOpen()) + throw new IllegalStateException(); + fetchNextIfNeeded(); + return next != null; + } + + @Override + public Event next() { + if (!walker.isOpen()) + throw new IllegalStateException(); + fetchNextIfNeeded(); + if (next == null) + throw new NoSuchElementException(); + Event result = next; + next = null; + return result; + } + + @Override + public void close() { + walker.close(); + } +} diff --git a/src/share/classes/java/nio/file/FileTreeWalker.java b/src/share/classes/java/nio/file/FileTreeWalker.java index 8ce95bc82403438f62af136926f61c7b2aba9434..85086e1e5a83af4939e3a8b04ae171739e43b74f 100644 --- a/src/share/classes/java/nio/file/FileTreeWalker.java +++ b/src/share/classes/java/nio/file/FileTreeWalker.java @@ -29,8 +29,8 @@ import java.nio.file.attribute.BasicFileAttributes; import java.io.Closeable; import java.io.IOException; import java.util.ArrayDeque; +import java.util.Collection; import java.util.Iterator; -import java.util.Set; import sun.nio.fs.BasicFileAttributesHolder; /** @@ -164,8 +164,17 @@ class FileTreeWalker implements Closeable { /** * Creates a {@code FileTreeWalker}. + * + * @throws IllegalArgumentException + * if {@code maxDepth} is negative + * @throws ClassCastException + * if (@code options} contains an element that is not a + * {@code FileVisitOption} + * @throws NullPointerException + * if {@code options} is {@ocde null} or the options + * array contains a {@code null} element */ - FileTreeWalker(Set options, int maxDepth) { + FileTreeWalker(Collection options, int maxDepth) { boolean fl = false; for (FileVisitOption option: options) { // will throw NPE if options contains null @@ -175,6 +184,9 @@ class FileTreeWalker implements Closeable { throw new AssertionError("Should not get here"); } } + if (maxDepth < 0) + throw new IllegalArgumentException("'maxDepth' is negative"); + this.followLinks = fl; this.linkOptions = (fl) ? new LinkOption[0] : new LinkOption[] { LinkOption.NOFOLLOW_LINKS }; diff --git a/src/share/classes/java/nio/file/Files.java b/src/share/classes/java/nio/file/Files.java index e2ef0fde4f484bf221a9e124ccb5030eed7b20b5..c4065690aa5e34c771d2859243be5f2ac8973762 100644 --- a/src/share/classes/java/nio/file/Files.java +++ b/src/share/classes/java/nio/file/Files.java @@ -25,10 +25,13 @@ package java.nio.file; +import java.nio.ByteBuffer; import java.nio.file.attribute.*; import java.nio.file.spi.FileSystemProvider; import java.nio.file.spi.FileTypeDetector; +import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; +import java.io.Closeable; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; @@ -38,7 +41,13 @@ import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.IOException; +import java.io.UncheckedIOException; import java.util.*; +import java.util.function.BiPredicate; +import java.util.stream.CloseableStream; +import java.util.stream.DelegatingStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import java.security.AccessController; import java.security.PrivilegedAction; import java.nio.charset.Charset; @@ -2596,9 +2605,6 @@ public final class Files { FileVisitor visitor) throws IOException { - if (maxDepth < 0) - throw new IllegalArgumentException("'maxDepth' is negative"); - /** * Create a FileTreeWalker to walk the file tree, invoking the visitor * for each event. @@ -2949,40 +2955,6 @@ public final class Files { } } - /** - * Read all the bytes from an input stream. The {@code initialSize} - * parameter indicates the initial size of the byte[] to allocate. - */ - private static byte[] read(InputStream source, int initialSize) - throws IOException - { - int capacity = initialSize; - byte[] buf = new byte[capacity]; - int nread = 0; - int rem = buf.length; - int n; - // read to EOF which may read more or less than initialSize (eg: file - // is truncated while we are reading) - while ((n = source.read(buf, nread, rem)) > 0) { - nread += n; - rem -= n; - assert rem >= 0; - if (rem == 0) { - // need larger buffer - int newCapacity = capacity << 1; - if (newCapacity < 0) { - if (capacity == Integer.MAX_VALUE) - throw new OutOfMemoryError("Required array size too large"); - newCapacity = Integer.MAX_VALUE; - } - rem = newCapacity - capacity; - buf = Arrays.copyOf(buf, newCapacity); - capacity = newCapacity; - } - } - return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); - } - /** * Read all the bytes from a file. The method ensures that the file is * closed when all bytes have been read or an I/O error, or other runtime @@ -3008,12 +2980,22 @@ public final class Files { * method is invoked to check read access to the file. */ public static byte[] readAllBytes(Path path) throws IOException { - long size = size(path); - if (size > (long)Integer.MAX_VALUE) - throw new OutOfMemoryError("Required array size too large"); + try (FileChannel fc = FileChannel.open(path)) { + long size = fc.size(); + if (size > (long)Integer.MAX_VALUE) + throw new OutOfMemoryError("Required array size too large"); + + byte[] arr = new byte[(int)size]; + ByteBuffer bb = ByteBuffer.wrap(arr); + while (bb.hasRemaining()) { + if (fc.read(bb) < 0) { + // truncated + break; + } + } - try (InputStream in = newInputStream(path)) { - return read(in, (int)size); + int nread = bb.position(); + return (nread == size) ? arr : Arrays.copyOf(arr, nread); } } @@ -3186,4 +3168,336 @@ public final class Files { } return path; } + + // -- Stream APIs -- + + /** + * Implementation of CloseableStream + */ + private static class DelegatingCloseableStream extends DelegatingStream + implements CloseableStream + { + private final Closeable closeable; + + DelegatingCloseableStream(Closeable c, Stream delegate) { + super(delegate); + this.closeable = c; + } + + public void close() { + try { + closeable.close(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } + + /** + * Return a lazily populated {@code CloseableStream}, the elements of + * which are the entries in the directory. The listing is not recursive. + * + *

The elements of the stream are {@link Path} objects that are + * obtained as if by {@link Path#resolve(Path) resolving} the name of the + * directory entry against {@code dir}. Some file systems maintain special + * links to the directory itself and the directory's parent directory. + * Entries representing these links are not included. + * + *

The stream is weakly consistent. It is thread safe but does + * not freeze the directory while iterating, so it may (or may not) + * reflect updates to the directory that occur after returning from this + * method. + * + *

When not using the try-with-resources construct, then the stream's + * {@link CloseableStream#close close} method should be invoked after the + * operation is completed so as to free any resources held for the open + * directory. Operating on a closed stream behaves as if the end of stream + * has been reached. Due to read-ahead, one or more elements may be + * returned after the stream has been closed. + * + *

If an {@link IOException} is thrown when accessing the directory + * after this method has returned, it is wrapped in an {@link + * UncheckedIOException} which will be thrown from the method that caused + * the access to take place. + * + * @param dir The path to the directory + * + * @return The {@code CloseableStream} describing the content of the + * directory + * + * @throws NotDirectoryException + * if the file could not otherwise be opened because it is not + * a directory (optional specific exception) + * @throws IOException + * if an I/O error occurs when opening the directory + * @throws SecurityException + * In the case of the default provider, and a security manager is + * installed, the {@link SecurityManager#checkRead(String) checkRead} + * method is invoked to check read access to the directory. + * + * @see #newDirectoryStream(Path) + * @since 1.8 + */ + public static CloseableStream list(Path dir) throws IOException { + DirectoryStream ds = Files.newDirectoryStream(dir); + final Iterator delegate = ds.iterator(); + + // Re-wrap DirectoryIteratorException to UncheckedIOException + Iterator it = new Iterator() { + public boolean hasNext() { + try { + return delegate.hasNext(); + } catch (DirectoryIteratorException e) { + throw new UncheckedIOException(e.getCause()); + } + } + public Path next() { + try { + return delegate.next(); + } catch (DirectoryIteratorException e) { + throw new UncheckedIOException(e.getCause()); + } + } + }; + + return new DelegatingCloseableStream<>(ds, + StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, + Spliterator.DISTINCT))); + } + + /** + * Return a {@code CloseableStream} that is lazily populated with {@code + * Path} by walking the file tree rooted at a given starting file. The + * file tree is traversed depth-first, the elements in the stream + * are {@link Path} objects that are obtained as if by {@link + * Path#resolve(Path) resolving} the relative path against {@code start}. + * + *

The {@code stream} walks the file tree as elements are consumed. + * The {@code CloseableStream} returned is guaranteed to have at least one + * element, the starting file itself. For each file visited, the stream + * attempts to read its {@link BasicFileAttributes}. If the file is a + * directory and can be opened successfully, entries in the directory, and + * their descendants will follow the directory in the stream as + * they are encountered. When all entries have been visited, then the + * directory is closed. The file tree walk then continues at the next + * sibling of the directory. + * + *

The stream is weakly consistent. It does not freeze the + * file tree while iterating, so it may (or may not) reflect updates to + * the file tree that occur after returned from this method. + * + *

By default, symbolic links are not automatically followed by this + * method. If the {@code options} parameter contains the {@link + * FileVisitOption#FOLLOW_LINKS FOLLOW_LINKS} option then symbolic links are + * followed. When following links, and the attributes of the target cannot + * be read, then this method attempts to get the {@code BasicFileAttributes} + * of the link. + * + *

If the {@code options} parameter contains the {@link + * FileVisitOption#FOLLOW_LINKS FOLLOW_LINKS} option then the stream keeps + * track of directories visited so that cycles can be detected. A cycle + * arises when there is an entry in a directory that is an ancestor of the + * directory. Cycle detection is done by recording the {@link + * java.nio.file.attribute.BasicFileAttributes#fileKey file-key} of directories, + * or if file keys are not available, by invoking the {@link #isSameFile + * isSameFile} method to test if a directory is the same file as an + * ancestor. When a cycle is detected it is treated as an I/O error with + * an instance of {@link FileSystemLoopException}. + * + *

The {@code maxDepth} parameter is the maximum number of levels of + * directories to visit. A value of {@code 0} means that only the starting + * file is visited, unless denied by the security manager. A value of + * {@link Integer#MAX_VALUE MAX_VALUE} may be used to indicate that all + * levels should be visited. + * + *

When a security manager is installed and it denies access to a file + * (or directory), then it is ignored and not included in the stream. + * + *

When not using the try-with-resources construct, then the stream's + * {@link CloseableStream#close close} method should be invoked after the + * operation is completed so as to free any resources held for the open + * directory. Operate the stream after it is closed will throw an + * {@link java.lang.IllegalStateException}. + * + *

If an {@link IOException} is thrown when accessing the directory + * after this method has returned, it is wrapped in an {@link + * UncheckedIOException} which will be thrown from the method that caused + * the access to take place. + * + * @param start + * the starting file + * @param maxDepth + * the maximum number of directory levels to visit + * @param options + * options to configure the traversal + * + * @return the {@link CloseableStream} of {@link Path} + * + * @throws IllegalArgumentException + * if the {@code maxDepth} parameter is negative + * @throws SecurityException + * If the security manager denies access to the starting file. + * In the case of the default provider, the {@link + * SecurityManager#checkRead(String) checkRead} method is invoked + * to check read access to the directory. + * @throws IOException + * if an I/O error is thrown when accessing the starting file. + * @since 1.8 + */ + public static CloseableStream walk(Path start, int maxDepth, + FileVisitOption... options) + throws IOException + { + FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options); + return new DelegatingCloseableStream<>(iterator, + StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT)) + .map(entry -> entry.file())); + } + + /** + * Return a {@code CloseableStream} that is lazily populated with {@code + * Path} by walking the file tree rooted at a given starting file. The + * file tree is traversed depth-first, the elements in the stream + * are {@link Path} objects that are obtained as if by {@link + * Path#resolve(Path) resolving} the relative path against {@code start}. + * + *

This method works as if invoking it were equivalent to evaluating the + * expression: + *

+     * walk(start, Integer.MAX_VALUE, options)
+     * 
+ * In other words, it visits all levels of the file tree. + * + * @param start + * the starting file + * @param options + * options to configure the traversal + * + * @return the {@link CloseableStream} of {@link Path} + * + * @throws SecurityException + * If the security manager denies access to the starting file. + * In the case of the default provider, the {@link + * SecurityManager#checkRead(String) checkRead} method is invoked + * to check read access to the directory. + * @throws IOException + * if an I/O error is thrown when accessing the starting file. + * + * @see #walk(Path, int, FileVisitOption...) + * @since 1.8 + */ + public static CloseableStream walk(Path start, + FileVisitOption... options) + throws IOException + { + return walk(start, Integer.MAX_VALUE, options); + } + + /** + * Return a {@code CloseableStream} that is lazily populated with {@code + * Path} by searching for files in a file tree rooted at a given starting + * file. + * + *

This method walks the file tree in exactly the manner specified by + * the {@link #walk walk} method. For each file encountered, the given + * {@link BiPredicate} is invoked with its {@link Path} and {@link + * BasicFileAttributes}. The {@code Path} object is obtained as if by + * {@link Path#resolve(Path) resolving} the relative path against {@code + * start} and is only included in the returned {@link CloseableStream} if + * the {@code BiPredicate} returns true. Compare to calling {@link + * java.util.stream.Stream#filter filter} on the {@code Stream} + * returned by {@code walk} method, this method may be more efficient by + * avoiding redundant retrieval of the {@code BasicFileAttributes}. + * + *

If an {@link IOException} is thrown when accessing the directory + * after returned from this method, it is wrapped in an {@link + * UncheckedIOException} which will be thrown from the method that caused + * the access to take place. + * + * @param start + * the starting file + * @param maxDepth + * the maximum number of directory levels to search + * @param matcher + * the function used to decide whether a file should be included + * in the returned stream + * @param options + * options to configure the traversal + * + * @return the {@link CloseableStream} of {@link Path} + * + * @throws IllegalArgumentException + * if the {@code maxDepth} parameter is negative + * @throws SecurityException + * If the security manager denies access to the starting file. + * In the case of the default provider, the {@link + * SecurityManager#checkRead(String) checkRead} method is invoked + * to check read access to the directory. + * @throws IOException + * if an I/O error is thrown when accessing the starting file. + * + * @see #walk(Path, int, FileVisitOption...) + * @since 1.8 + */ + public static CloseableStream find(Path start, + int maxDepth, + BiPredicate matcher, + FileVisitOption... options) + throws IOException + { + FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options); + return new DelegatingCloseableStream<>(iterator, + StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT)) + .filter(entry -> matcher.test(entry.file(), entry.attributes())) + .map(entry -> entry.file())); + } + + /** + * Read all lines from a file as a {@code CloseableStream}. Unlike {@link + * #readAllLines(Path, Charset) readAllLines}, this method does not read + * all lines into a {@code List}, but instead populates lazily as the stream + * is consumed. + * + *

Bytes from the file are decoded into characters using the specified + * charset and the same line terminators as specified by {@code + * readAllLines} are supported. + * + *

After this method returns, then any subsequent I/O exception that + * occurs while reading from the file or when a malformed or unmappable byte + * sequence is read, is wrapped in an {@link UncheckedIOException} that will + * be thrown form the + * {@link java.util.stream.Stream} method that caused the read to take + * place. In case an {@code IOException} is thrown when closing the file, + * it is also wrapped as an {@code UncheckedIOException}. + * + *

When not using the try-with-resources construct, then stream's + * {@link CloseableStream#close close} method should be invoked after + * operation is completed so as to free any resources held for the open + * file. + * + * @param path + * the path to the file + * @param cs + * the charset to use for decoding + * + * @return the lines from the file as a {@code CloseableStream} + * + * @throws IOException + * if an I/O error occurs opening the file + * @throws SecurityException + * In the case of the default provider, and a security manager is + * installed, the {@link SecurityManager#checkRead(String) checkRead} + * method is invoked to check read access to the file. + * + * @see #readAllLines(Path, Charset) + * @see #newBufferedReader(Path, Charset) + * @see java.io.BufferedReader#lines() + * @since 1.8 + */ + public static CloseableStream lines(Path path, Charset cs) + throws IOException + { + BufferedReader br = Files.newBufferedReader(path, cs); + return new DelegatingCloseableStream<>(br, br.lines()); + } } diff --git a/src/share/classes/java/security/AccessControlContext.java b/src/share/classes/java/security/AccessControlContext.java index d916c57c41176dde556d55c38df3933462620329..349de30f01edf476246fb5e107b99fa66ae2d853 100644 --- a/src/share/classes/java/security/AccessControlContext.java +++ b/src/share/classes/java/security/AccessControlContext.java @@ -88,6 +88,15 @@ public final class AccessControlContext { private DomainCombiner combiner = null; + // limited privilege scope + private Permission permissions[]; + private AccessControlContext parent; + private boolean isWrapped; + + // is constrained by limited privilege scope? + private boolean isLimited; + private ProtectionDomain limitedContext[]; + private static boolean debugInit = false; private static Debug debug = null; @@ -182,15 +191,80 @@ public final class AccessControlContext { /** * package private for AccessController + * + * This "argument wrapper" context will be passed as the actual context + * parameter on an internal doPrivileged() call used in the implementation. */ - AccessControlContext(ProtectionDomain context[], DomainCombiner combiner) { + AccessControlContext(ProtectionDomain caller, DomainCombiner combiner, + AccessControlContext parent, AccessControlContext context, + Permission[] perms) + { + /* + * Combine the domains from the doPrivileged() context into our + * wrapper context, if necessary. + */ + ProtectionDomain[] callerPDs = null; + if (caller != null) { + callerPDs = new ProtectionDomain[] { caller }; + } if (context != null) { - this.context = context.clone(); + if (combiner != null) { + this.context = combiner.combine(callerPDs, context.context); + } else { + this.context = combine(callerPDs, context.context); + } + } else { + /* + * Call combiner even if there is seemingly nothing to combine. + */ + if (combiner != null) { + this.context = combiner.combine(callerPDs, null); + } else { + this.context = combine(callerPDs, null); + } } this.combiner = combiner; + + Permission[] tmp = null; + if (perms != null) { + tmp = new Permission[perms.length]; + for (int i=0; i < perms.length; i++) { + if (perms[i] == null) { + throw new NullPointerException("permission can't be null"); + } + + /* + * An AllPermission argument is equivalent to calling + * doPrivileged() without any limit permissions. + */ + if (perms[i].getClass() == AllPermission.class) { + parent = null; + } + tmp[i] = perms[i]; + } + } + + /* + * For a doPrivileged() with limited privilege scope, initialize + * the relevant fields. + * + * The limitedContext field contains the union of all domains which + * are enclosed by this limited privilege scope. In other words, + * it contains all of the domains which could potentially be checked + * if none of the limiting permissions implied a requested permission. + */ + if (parent != null) { + this.limitedContext = combine(parent.context, parent.limitedContext); + this.isLimited = true; + this.isWrapped = true; + this.permissions = tmp; + this.parent = parent; + this.privilegedContext = context; // used in checkPermission2() + } this.isAuthorized = true; } + /** * package private constructor for AccessController.getContext() */ @@ -266,6 +340,13 @@ public final class AccessControlContext { if (sm != null) { sm.checkPermission(SecurityConstants.GET_COMBINER_PERMISSION); } + return getCombiner(); + } + + /** + * package private for AccessController + */ + DomainCombiner getCombiner() { return combiner; } @@ -341,8 +422,10 @@ public final class AccessControlContext { or the first domain was a Privileged system domain. This is to make the common case for system code very fast */ - if (context == null) + if (context == null) { + checkPermission2(perm); return; + } for (int i=0; i< context.length; i++) { if (context[i] != null && !context[i].implies(perm)) { @@ -376,20 +459,108 @@ public final class AccessControlContext { debug.println("access allowed "+perm); } - return; + checkPermission2(perm); + } + + /* + * Check the domains associated with the limited privilege scope. + */ + private void checkPermission2(Permission perm) { + if (!isLimited) { + return; + } + + /* + * Check the doPrivileged() context parameter, if present. + */ + if (privilegedContext != null) { + privilegedContext.checkPermission2(perm); + } + + /* + * Ignore the limited permissions and parent fields of a wrapper + * context since they were already carried down into the unwrapped + * context. + */ + if (isWrapped) { + return; + } + + /* + * Try to match any limited privilege scope. + */ + if (permissions != null) { + Class permClass = perm.getClass(); + for (int i=0; i < permissions.length; i++) { + Permission limit = permissions[i]; + if (limit.getClass().equals(permClass) && limit.implies(perm)) { + return; + } + } + } + + /* + * Check the limited privilege scope up the call stack or the inherited + * parent thread call stack of this ACC. + */ + if (parent != null) { + /* + * As an optimization, if the parent context is the inherited call + * stack context from a parent thread then checking the protection + * domains of the parent context is redundant since they have + * already been merged into the child thread's context by + * optimize(). When parent is set to an inherited context this + * context was not directly created by a limited scope + * doPrivileged() and it does not have its own limited permissions. + */ + if (permissions == null) { + parent.checkPermission2(perm); + } else { + parent.checkPermission(perm); + } + } } /** * Take the stack-based context (this) and combine it with the - * privileged or inherited context, if need be. + * privileged or inherited context, if need be. Any limited + * privilege scope is flagged regardless of whether the assigned + * context comes from an immediately enclosing limited doPrivileged(). + * The limited privilege scope can indirectly flow from the inherited + * parent thread or an assigned context previously captured by getContext(). */ AccessControlContext optimize() { // the assigned (privileged or inherited) context AccessControlContext acc; + DomainCombiner combiner = null; + AccessControlContext parent = null; + Permission[] permissions = null; + if (isPrivileged) { acc = privilegedContext; + if (acc != null) { + /* + * If the context is from a limited scope doPrivileged() then + * copy the permissions and parent fields out of the wrapper + * context that was created to hold them. + */ + if (acc.isWrapped) { + permissions = acc.permissions; + parent = acc.parent; + } + } } else { acc = AccessController.getInheritedAccessControlContext(); + if (acc != null) { + /* + * If the inherited context is constrained by a limited scope + * doPrivileged() then set it as our parent so we will process + * the non-domain-related state. + */ + if (acc.isLimited) { + parent = acc; + } + } } // this.context could be null if only system code is on the stack; @@ -399,53 +570,98 @@ public final class AccessControlContext { // acc.context could be null if only system code was involved; // in that case, ignore the assigned context boolean skipAssigned = (acc == null || acc.context == null); + ProtectionDomain[] assigned = (skipAssigned) ? null : acc.context; + ProtectionDomain[] pd; + + // if there is no enclosing limited privilege scope on the stack or + // inherited from a parent thread + boolean skipLimited = ((acc == null || !acc.isWrapped) && parent == null); if (acc != null && acc.combiner != null) { // let the assigned acc's combiner do its thing - return goCombiner(context, acc); - } + if (getDebug() != null) { + debug.println("AccessControlContext invoking the Combiner"); + } - // optimization: if neither have contexts; return acc if possible - // rather than this, because acc might have a combiner - if (skipAssigned && skipStack) { - return this; - } + // No need to clone current and assigned.context + // combine() will not update them + combiner = acc.combiner; + pd = combiner.combine(context, assigned); + } else { + if (skipStack) { + if (skipAssigned) { + calculateFields(acc, parent, permissions); + return this; + } else if (skipLimited) { + return acc; + } + } else if (assigned != null) { + if (skipLimited) { + // optimization: if there is a single stack domain and + // that domain is already in the assigned context; no + // need to combine + if (context.length == 1 && context[0] == assigned[0]) { + return acc; + } + } + } - // optimization: if there is no stack context; there is no reason - // to compress the assigned context, it already is compressed - if (skipStack) { - return acc; + pd = combine(context, assigned); + if (skipLimited && !skipAssigned && pd == assigned) { + return acc; + } else if (skipAssigned && pd == context) { + calculateFields(acc, parent, permissions); + return this; + } } - int slen = context.length; + // Reuse existing ACC + this.context = pd; + this.combiner = combiner; + this.isPrivileged = false; + + calculateFields(acc, parent, permissions); + return this; + } + + + /* + * Combine the current (stack) and assigned domains. + */ + private static ProtectionDomain[] combine(ProtectionDomain[]current, + ProtectionDomain[] assigned) { + + // current could be null if only system code is on the stack; + // in that case, ignore the stack context + boolean skipStack = (current == null); + + // assigned could be null if only system code was involved; + // in that case, ignore the assigned context + boolean skipAssigned = (assigned == null); + + int slen = (skipStack) ? 0 : current.length; // optimization: if there is no assigned context and the stack length // is less then or equal to two; there is no reason to compress the // stack context, it already is if (skipAssigned && slen <= 2) { - return this; + return current; } - // optimization: if there is a single stack domain and that domain - // is already in the assigned context; no need to combine - if ((slen == 1) && (context[0] == acc.context[0])) { - return acc; - } - - int n = (skipAssigned) ? 0 : acc.context.length; + int n = (skipAssigned) ? 0 : assigned.length; // now we combine both of them, and create a new context ProtectionDomain pd[] = new ProtectionDomain[slen + n]; // first copy in the assigned context domains, no need to compress if (!skipAssigned) { - System.arraycopy(acc.context, 0, pd, 0, n); + System.arraycopy(assigned, 0, pd, 0, n); } // now add the stack context domains, discarding nulls and duplicates outer: - for (int i = 0; i < context.length; i++) { - ProtectionDomain sd = context[i]; + for (int i = 0; i < slen; i++) { + ProtectionDomain sd = current[i]; if (sd != null) { for (int j = 0; j < n; j++) { if (sd == pd[j]) { @@ -459,55 +675,48 @@ public final class AccessControlContext { // if length isn't equal, we need to shorten the array if (n != pd.length) { // optimization: if we didn't really combine anything - if (!skipAssigned && n == acc.context.length) { - return acc; + if (!skipAssigned && n == assigned.length) { + return assigned; } else if (skipAssigned && n == slen) { - return this; + return current; } ProtectionDomain tmp[] = new ProtectionDomain[n]; System.arraycopy(pd, 0, tmp, 0, n); pd = tmp; } - // return new AccessControlContext(pd, false); - - // Reuse existing ACC - - this.context = pd; - this.combiner = null; - this.isPrivileged = false; - - return this; + return pd; } - private AccessControlContext goCombiner(ProtectionDomain[] current, - AccessControlContext assigned) { - - // the assigned ACC's combiner is not null -- - // let the combiner do its thing - // XXX we could add optimizations to 'current' here ... - - if (getDebug() != null) { - debug.println("AccessControlContext invoking the Combiner"); + /* + * Calculate the additional domains that could potentially be reached via + * limited privilege scope. Mark the context as being subject to limited + * privilege scope unless the reachable domains (if any) are already + * contained in this domain context (in which case any limited + * privilege scope checking would be redundant). + */ + private void calculateFields(AccessControlContext assigned, + AccessControlContext parent, Permission[] permissions) + { + ProtectionDomain[] parentLimit = null; + ProtectionDomain[] assignedLimit = null; + ProtectionDomain[] newLimit; + + parentLimit = (parent != null)? parent.limitedContext: null; + assignedLimit = (assigned != null)? assigned.limitedContext: null; + newLimit = combine(parentLimit, assignedLimit); + if (newLimit != null) { + if (context == null || !containsAllPDs(newLimit, context)) { + this.limitedContext = newLimit; + this.permissions = permissions; + this.parent = parent; + this.isLimited = true; + } } - - // No need to clone current and assigned.context - // combine() will not update them - ProtectionDomain[] combinedPds = assigned.combiner.combine( - current, assigned.context); - - // return new AccessControlContext(combinedPds, assigned.combiner); - - // Reuse existing ACC - this.context = combinedPds; - this.combiner = assigned.combiner; - this.isPrivileged = false; - this.isAuthorized = assigned.isAuthorized; - - return this; } + /** * Checks two AccessControlContext objects for equality. * Checks that obj is @@ -527,31 +736,131 @@ public final class AccessControlContext { AccessControlContext that = (AccessControlContext) obj; + if (!equalContext(that)) + return false; + + if (!equalLimitedContext(that)) + return false; - if (context == null) { - return (that.context == null); + return true; + } + + /* + * Compare for equality based on state that is free of limited + * privilege complications. + */ + private boolean equalContext(AccessControlContext that) { + if (!equalPDs(this.context, that.context)) + return false; + + if (this.combiner == null && that.combiner != null) + return false; + + if (this.combiner != null && !this.combiner.equals(that.combiner)) + return false; + + return true; + } + + private boolean equalPDs(ProtectionDomain[] a, ProtectionDomain[] b) { + if (a == null) { + return (b == null); + } + + if (b == null) + return false; + + if (!(containsAllPDs(a, b) && containsAllPDs(b, a))) + return false; + + return true; + } + + /* + * Compare for equality based on state that is captured during a + * call to AccessController.getContext() when a limited privilege + * scope is in effect. + */ + private boolean equalLimitedContext(AccessControlContext that) { + if (that == null) + return false; + + /* + * If neither instance has limited privilege scope then we're done. + */ + if (!this.isLimited && !that.isLimited) + return true; + + /* + * If only one instance has limited privilege scope then we're done. + */ + if (!(this.isLimited && that.isLimited)) + return false; + + /* + * Wrapped instances should never escape outside the implementation + * this class and AccessController so this will probably never happen + * but it only makes any sense to compare if they both have the same + * isWrapped state. + */ + if ((this.isWrapped && !that.isWrapped) || + (!this.isWrapped && that.isWrapped)) { + return false; } - if (that.context == null) + if (this.permissions == null && that.permissions != null) return false; - if (!(this.containsAllPDs(that) && that.containsAllPDs(this))) + if (this.permissions != null && that.permissions == null) + return false; + + if (!(this.containsAllLimits(that) && that.containsAllLimits(this))) + return false; + + /* + * Skip through any wrapped contexts. + */ + AccessControlContext thisNextPC = getNextPC(this); + AccessControlContext thatNextPC = getNextPC(that); + + /* + * The protection domains and combiner of a privilegedContext are + * not relevant because they have already been included in the context + * of this instance by optimize() so we only care about any limited + * privilege state they may have. + */ + if (thisNextPC == null && thatNextPC != null && thatNextPC.isLimited) return false; - if (this.combiner == null) - return (that.combiner == null); + if (thisNextPC != null && !thisNextPC.equalLimitedContext(thatNextPC)) + return false; - if (that.combiner == null) + if (this.parent == null && that.parent != null) return false; - if (!this.combiner.equals(that.combiner)) + if (this.parent != null && !this.parent.equals(that.parent)) return false; return true; } - private boolean containsAllPDs(AccessControlContext that) { + /* + * Follow the privilegedContext link making our best effort to skip + * through any wrapper contexts. + */ + private static AccessControlContext getNextPC(AccessControlContext acc) { + while (acc != null && acc.privilegedContext != null) { + acc = acc.privilegedContext; + if (!acc.isWrapped) + return acc; + } + return null; + } + + private static boolean containsAllPDs(ProtectionDomain[] thisContext, + ProtectionDomain[] thatContext) { boolean match = false; + // // ProtectionDomains within an ACC currently cannot be null // and this is enforced by the constructor and the various @@ -559,17 +868,17 @@ public final class AccessControlContext { // to support the notion of a null PD and therefore this logic continues // to support that notion. ProtectionDomain thisPd; - for (int i = 0; i < context.length; i++) { + for (int i = 0; i < thisContext.length; i++) { match = false; - if ((thisPd = context[i]) == null) { - for (int j = 0; (j < that.context.length) && !match; j++) { - match = (that.context[j] == null); + if ((thisPd = thisContext[i]) == null) { + for (int j = 0; (j < thatContext.length) && !match; j++) { + match = (thatContext[j] == null); } } else { Class thisPdClass = thisPd.getClass(); ProtectionDomain thatPd; - for (int j = 0; (j < that.context.length) && !match; j++) { - thatPd = that.context[j]; + for (int j = 0; (j < thatContext.length) && !match; j++) { + thatPd = thatContext[j]; // Class check required to avoid PD exposure (4285406) match = (thatPd != null && @@ -580,6 +889,29 @@ public final class AccessControlContext { } return match; } + + private boolean containsAllLimits(AccessControlContext that) { + boolean match = false; + Permission thisPerm; + + if (this.permissions == null && that.permissions == null) + return true; + + for (int i = 0; i < this.permissions.length; i++) { + Permission limit = this.permissions[i]; + Class limitClass = limit.getClass(); + match = false; + for (int j = 0; (j < that.permissions.length) && !match; j++) { + Permission perm = that.permissions[j]; + match = (limitClass.equals(perm.getClass()) && + limit.equals(perm)); + } + if (!match) return false; + } + return match; + } + + /** * Returns the hash code value for this context. The hash code * is computed by exclusive or-ing the hash code of all the protection @@ -598,6 +930,7 @@ public final class AccessControlContext { if (context[i] != null) hashCode ^= context[i].hashCode(); } + return hashCode; } } diff --git a/src/share/classes/java/security/AccessController.java b/src/share/classes/java/security/AccessController.java index bdb0e8fa8350d7356b14c0f576a0716f60a62ea3..a70251664a52f586b480a723f4d09de613929690 100644 --- a/src/share/classes/java/security/AccessController.java +++ b/src/share/classes/java/security/AccessController.java @@ -82,9 +82,15 @@ import sun.reflect.Reflection; * else if (caller i is marked as privileged) { * if (a context was specified in the call to doPrivileged) * context.checkPermission(permission) - * return; + * if (limited permissions were specified in the call to doPrivileged) { + * for (each limited permission) { + * if (the limited permission implies the requested permission) + * return; + * } + * } else + * return; * } - * }; + * } * * // Next, check the context inherited when the thread was created. * // Whenever a new thread is created, the AccessControlContext at @@ -101,11 +107,16 @@ import sun.reflect.Reflection; * was marked as "privileged" via a doPrivileged * call without a context argument (see below for information about a * context argument). If that caller's domain has the - * specified permission, no further checking is done and + * specified permission and at least one limiting permission argument (if any) + * implies the requested permission, no further checking is done and * checkPermission * returns quietly, indicating that the requested access is allowed. * If that domain does not have the specified permission, an exception - * is thrown, as usual. + * is thrown, as usual. If the caller's domain had the specified permission + * but it was not implied by any limiting permission arguments given in the call + * to doPrivileged then the permission checking continues + * until there are no more callers or another doPrivileged + * call matches the requested permission and returns normally. * *

The normal use of the "privileged" feature is as follows. If you * don't need to return a value from within the "privileged" block, do @@ -180,6 +191,9 @@ import sun.reflect.Reflection; * *

Be *very* careful in your use of the "privileged" construct, and * always remember to make the privileged code section as small as possible. + * You can pass Permission arguments to further limit the + * scope of the "privilege" (see below). + * * *

Note that checkPermission always performs security checks * within the context of the currently executing thread. @@ -215,7 +229,9 @@ import sun.reflect.Reflection; * *

There are also times where you don't know a priori which permissions * to check the context against. In these cases you can use the - * doPrivileged method that takes a context: + * doPrivileged method that takes a context. You can also limit the scope + * of the privileged code by passing additional Permission + * parameters. * *

 {@code
  * somemethod() {
@@ -223,12 +239,21 @@ import sun.reflect.Reflection;
  *         public Object run() {
  *             // Code goes here. Any permission checks within this
  *             // run method will require that the intersection of the
- *             // callers protection domain and the snapshot's
- *             // context have the desired permission.
+ *             // caller's protection domain and the snapshot's
+ *             // context have the desired permission. If a requested
+ *             // permission is not implied by the limiting FilePermission
+ *             // argument then checking of the thread continues beyond the
+ *             // caller of doPrivileged.
  *         }
- *     }, acc);
+ *     }, acc, new FilePermission("/temp/*", read));
  *     ...normal code here...
  * }}
+ *

Passing a limiting Permission argument of an instance of + * AllPermission is equivalent to calling the equivalent + * doPrivileged method without limiting Permission + * arguments. Passing a zero length array of Permission disables + * the code privileges so that checking always continues beyond the caller of + * that doPrivileged method. * * @see AccessControlContext * @@ -337,6 +362,112 @@ public final class AccessController { public static native T doPrivileged(PrivilegedAction action, AccessControlContext context); + + /** + * Performs the specified PrivilegedAction with privileges + * enabled and restricted by the specified + * AccessControlContext and with a privilege scope limited + * by specified Permission arguments. + * + * The action is performed with the intersection of the permissions + * possessed by the caller's protection domain, and those possessed + * by the domains represented by the specified + * AccessControlContext. + *

+ * If the action's run method throws an (unchecked) exception, + * it will propagate through this method. + * + * @param action the action to be performed. + * @param context an access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * @param perms the Permission arguments which limit the + * scope of the caller's privileges. The number of arguments + * is variable. + * + * @return the value returned by the action's run method. + * + * @throws NullPointerException if action or perms or any element of + * perms is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + * + * @since 1.8 + */ + @CallerSensitive + public static T doPrivileged(PrivilegedAction action, + AccessControlContext context, Permission... perms) { + + AccessControlContext parent = getContext(); + if (perms == null) { + throw new NullPointerException("null permissions parameter"); + } + Class caller = Reflection.getCallerClass(); + return AccessController.doPrivileged(action, createWrapper(null, + caller, parent, context, perms)); + } + + + /** + * Performs the specified PrivilegedAction with privileges + * enabled and restricted by the specified + * AccessControlContext and with a privilege scope limited + * by specified Permission arguments. + * + * The action is performed with the intersection of the permissions + * possessed by the caller's protection domain, and those possessed + * by the domains represented by the specified + * AccessControlContext. + *

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

This method preserves the current AccessControlContext's + * DomainCombiner (which may be null) while the action is performed. + * + * @param action the action to be performed. + * @param context an access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * @param perms the Permission arguments which limit the + * scope of the caller's privileges. The number of arguments + * is variable. + * + * @return the value returned by the action's run method. + * + * @throws NullPointerException if action or perms or any element of + * perms is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + * @see java.security.DomainCombiner + * + * @since 1.8 + */ + @CallerSensitive + public static T doPrivilegedWithCombiner(PrivilegedAction action, + AccessControlContext context, Permission... perms) { + + AccessControlContext parent = getContext(); + DomainCombiner dc = parent.getCombiner(); + if (dc == null && context != null) { + dc = context.getCombiner(); + } + if (perms == null) { + throw new NullPointerException("null permissions parameter"); + } + Class caller = Reflection.getCallerClass(); + return AccessController.doPrivileged(action, createWrapper(dc, caller, + parent, context, perms)); + } + /** * Performs the specified PrivilegedExceptionAction with * privileges enabled. The action is performed with all of the @@ -411,6 +542,22 @@ public final class AccessController { private static AccessControlContext preserveCombiner(DomainCombiner combiner, Class caller) { + return createWrapper(combiner, caller, null, null, null); + } + + /** + * Create a wrapper to contain the limited privilege scope data. + */ + private static AccessControlContext + createWrapper(DomainCombiner combiner, Class caller, + AccessControlContext parent, AccessControlContext context, + Permission[] perms) + { + return new AccessControlContext(getCallerPD(caller), combiner, parent, + context, perms); + } + + private static ProtectionDomain getCallerPD(final Class caller) { ProtectionDomain callerPd = doPrivileged (new PrivilegedAction() { public ProtectionDomain run() { @@ -418,18 +565,9 @@ public final class AccessController { } }); - // perform 'combine' on the caller of doPrivileged, - // even if the caller is from the bootclasspath - ProtectionDomain[] pds = new ProtectionDomain[] {callerPd}; - if (combiner == null) { - return new AccessControlContext(pds); - } else { - return new AccessControlContext(combiner.combine(pds, null), - combiner); - } + return callerPd; } - /** * Performs the specified {@code PrivilegedExceptionAction} with * privileges enabled and restricted by the specified @@ -461,7 +599,7 @@ public final class AccessController { * @exception NullPointerException if the action is {@code null} * * @see #doPrivileged(PrivilegedAction) - * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + * @see #doPrivileged(PrivilegedAction,AccessControlContext) */ @CallerSensitive public static native T @@ -469,6 +607,118 @@ public final class AccessController { AccessControlContext context) throws PrivilegedActionException; + + /** + * Performs the specified PrivilegedExceptionAction with + * privileges enabled and restricted by the specified + * AccessControlContext and with a privilege scope limited by + * specified Permission arguments. + * + * The action is performed with the intersection of the permissions + * possessed by the caller's protection domain, and those possessed + * by the domains represented by the specified + * AccessControlContext. + *

+ * If the action's run method throws an (unchecked) exception, + * it will propagate through this method. + * + * @param action the action to be performed. + * @param context an access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * @param perms the Permission arguments which limit the + * scope of the caller's privileges. The number of arguments + * is variable. + * + * @return the value returned by the action's run method. + * + * @throws PrivilegedActionException if the specified action's + * run method threw a checked exception + * @throws NullPointerException if action or perms or any element of + * perms is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedAction,AccessControlContext) + * + * @since 1.8 + */ + @CallerSensitive + public static T doPrivileged(PrivilegedExceptionAction action, + AccessControlContext context, Permission... perms) + throws PrivilegedActionException + { + AccessControlContext parent = getContext(); + if (perms == null) { + throw new NullPointerException("null permissions parameter"); + } + Class caller = Reflection.getCallerClass(); + return AccessController.doPrivileged(action, createWrapper(null, caller, parent, context, perms)); + } + + + /** + * Performs the specified PrivilegedExceptionAction with + * privileges enabled and restricted by the specified + * AccessControlContext and with a privilege scope limited by + * specified Permission arguments. + * + * The action is performed with the intersection of the permissions + * possessed by the caller's protection domain, and those possessed + * by the domains represented by the specified + * AccessControlContext. + *

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

This method preserves the current AccessControlContext's + * DomainCombiner (which may be null) while the action is performed. + * + * @param action the action to be performed. + * @param context an access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * @param perms the Permission arguments which limit the + * scope of the caller's privileges. The number of arguments + * is variable. + * + * @return the value returned by the action's run method. + * + * @throws PrivilegedActionException if the specified action's + * run method threw a checked exception + * @throws NullPointerException if action or perms or any element of + * perms is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedAction,AccessControlContext) + * @see java.security.DomainCombiner + * + * @since 1.8 + */ + @CallerSensitive + public static T doPrivilegedWithCombiner(PrivilegedExceptionAction action, + AccessControlContext context, + Permission... perms) + throws PrivilegedActionException + { + AccessControlContext parent = getContext(); + DomainCombiner dc = parent.getCombiner(); + if (dc == null && context != null) { + dc = context.getCombiner(); + } + if (perms == null) { + throw new NullPointerException("null permissions parameter"); + } + Class caller = Reflection.getCallerClass(); + return AccessController.doPrivileged(action, createWrapper(dc, caller, + parent, context, perms)); + } + /** * Returns the AccessControl context. i.e., it gets * the protection domains of all the callers on the stack, @@ -481,6 +731,7 @@ public final class AccessController { private static native AccessControlContext getStackAccessControlContext(); + /** * Returns the "inherited" AccessControl context. This is the context * that existed when the thread was created. Package private so @@ -491,9 +742,9 @@ public final class AccessController { /** * This method takes a "snapshot" of the current calling context, which - * includes the current Thread's inherited AccessControlContext, - * and places it in an AccessControlContext object. This context may then - * be checked at a later point, possibly in another thread. + * includes the current Thread's inherited AccessControlContext and any + * limited privilege scope, and places it in an AccessControlContext object. + * This context may then be checked at a later point, possibly in another thread. * * @see AccessControlContext * @@ -531,7 +782,7 @@ public final class AccessController { */ public static void checkPermission(Permission perm) - throws AccessControlException + throws AccessControlException { //System.err.println("checkPermission "+perm); //Thread.currentThread().dumpStack(); diff --git a/src/share/classes/java/security/DigestOutputStream.java b/src/share/classes/java/security/DigestOutputStream.java index 1307bdff3448bc574f8fd7881778271fad727151..31b77259ea6f78c1bdab9278559fdaf20c5606da 100644 --- a/src/share/classes/java/security/DigestOutputStream.java +++ b/src/share/classes/java/security/DigestOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 1999, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -112,10 +112,10 @@ public class DigestOutputStream extends FilterOutputStream { * @see MessageDigest#update(byte) */ public void write(int b) throws IOException { + out.write(b); if (on) { digest.update((byte)b); } - out.write(b); } /** @@ -142,10 +142,10 @@ public class DigestOutputStream extends FilterOutputStream { * @see MessageDigest#update(byte[], int, int) */ public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); if (on) { digest.update(b, off, len); } - out.write(b, off, len); } /** diff --git a/src/share/classes/java/text/DateFormatSymbols.java b/src/share/classes/java/text/DateFormatSymbols.java index 80e4f03a38a727b51bdd275bd3570da812f10096..3357b49d774aa553bc0f68a61f4fd3b511d57d0a 100644 --- a/src/share/classes/java/text/DateFormatSymbols.java +++ b/src/share/classes/java/text/DateFormatSymbols.java @@ -59,7 +59,7 @@ import sun.util.locale.provider.TimeZoneNameUtility; * DateFormatSymbols is a public class for encapsulating * localizable date-time formatting data, such as the names of the * months, the names of the days of the week, and the time zone data. - * DateFormat and SimpleDateFormat both use + * SimpleDateFormat uses * DateFormatSymbols to encapsulate this information. * *

diff --git a/src/share/classes/java/text/DigitList.java b/src/share/classes/java/text/DigitList.java index da5e78d65b34ac4c617ca178c4ae75d573146fa9..5f4321c025b3791d9737a6bea37d4566e795f6cf 100644 --- a/src/share/classes/java/text/DigitList.java +++ b/src/share/classes/java/text/DigitList.java @@ -271,7 +271,7 @@ final class DigitList implements Cloneable { * @param maximumFractionDigits The most fractional digits which should * be converted. */ - public final void set(boolean isNegative, double source, int maximumFractionDigits) { + final void set(boolean isNegative, double source, int maximumFractionDigits) { set(isNegative, source, maximumFractionDigits, true); } @@ -288,10 +288,11 @@ final class DigitList implements Cloneable { */ final void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) { - FloatingDecimal fd = new FloatingDecimal(source); - boolean hasBeenRoundedUp = fd.digitsRoundedUp(); - boolean allDecimalDigits = fd.decimalDigitsExact(); - String digitsString = fd.toJavaFormatString(); + FloatingDecimal.BinaryToASCIIConverter fdConverter = FloatingDecimal.getBinaryToASCIIConverter(source); + boolean hasBeenRoundedUp = fdConverter.digitsRoundedUp(); + boolean allDecimalDigits = fdConverter.decimalDigitsExact(); + assert !fdConverter.isExceptional(); + String digitsString = fdConverter.toJavaFormatString(); set(isNegative, digitsString, hasBeenRoundedUp, allDecimalDigits, @@ -305,9 +306,9 @@ final class DigitList implements Cloneable { * @param allDecimalDigits Boolean value indicating if the digits in s are * an exact decimal representation of the double that was passed. */ - final void set(boolean isNegative, String s, - boolean roundedUp, boolean allDecimalDigits, - int maximumDigits, boolean fixedPoint) { + private void set(boolean isNegative, String s, + boolean roundedUp, boolean allDecimalDigits, + int maximumDigits, boolean fixedPoint) { this.isNegative = isNegative; int len = s.length(); char[] source = getDataChars(len); @@ -607,7 +608,7 @@ final class DigitList implements Cloneable { /** * Utility routine to set the value of the digit list from a long */ - public final void set(boolean isNegative, long source) { + final void set(boolean isNegative, long source) { set(isNegative, source, 0); } @@ -620,7 +621,7 @@ final class DigitList implements Cloneable { * If maximumDigits is lower than the number of significant digits * in source, the representation will be rounded. Ignored if <= 0. */ - public final void set(boolean isNegative, long source, int maximumDigits) { + final void set(boolean isNegative, long source, int maximumDigits) { this.isNegative = isNegative; // This method does not expect a negative number. However, diff --git a/src/share/classes/java/text/SimpleDateFormat.java b/src/share/classes/java/text/SimpleDateFormat.java index 4e85d0d5651e4f910432cb379662b7784690beed..9c5aaa35561493b1c6cb79cbc2926d3f99e53783 100644 --- a/src/share/classes/java/text/SimpleDateFormat.java +++ b/src/share/classes/java/text/SimpleDateFormat.java @@ -1845,6 +1845,8 @@ public class SimpleDateFormat extends DateFormat { } ++pos.index; } + // Remember the actual start index + int actualStart = pos.index; parsing: { @@ -1924,9 +1926,9 @@ public class SimpleDateFormat extends DateFormat { // we made adjustments to place the 2-digit year in the proper // century, for parsed strings from "00" to "99". Any other string // is treated literally: "2250", "-1", "1", "002". - if (count <= 2 && (pos.index - start) == 2 - && Character.isDigit(text.charAt(start)) - && Character.isDigit(text.charAt(start+1))) { + if (count <= 2 && (pos.index - actualStart) == 2 + && Character.isDigit(text.charAt(actualStart)) + && Character.isDigit(text.charAt(actualStart + 1))) { // Assume for example that the defaultCenturyStart is 6/18/1903. // This means that two-digit years will be forced into the range // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02 diff --git a/src/share/classes/java/util/Arrays.java b/src/share/classes/java/util/Arrays.java index af0663bff1a394f5430914a5b6357078a46fe825..4035666d2ab7130153b6e069ae15fd0be0c56f84 100644 --- a/src/share/classes/java/util/Arrays.java +++ b/src/share/classes/java/util/Arrays.java @@ -40,7 +40,6 @@ import java.util.stream.IntStream; import java.util.stream.LongStream; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import static java.util.ArraysParallelSortHelpers.*; /** * This class contains various methods for manipulating arrays (such as @@ -70,17 +69,62 @@ import static java.util.ArraysParallelSortHelpers.*; public class Arrays { /** - * The minimum array length below which the sorting algorithm will not - * further partition the sorting task. + * The minimum array length below which a parallel sorting + * algorithm will not further partition the sorting task. Using + * smaller sizes typically results in memory contention across + * tasks that makes parallel speedups unlikely. */ - // reasonable default so that we don't overcreate tasks - private static final int MIN_ARRAY_SORT_GRAN = 256; + private static final int MIN_ARRAY_SORT_GRAN = 1 << 13; // Suppresses default constructor, ensuring non-instantiability. private Arrays() {} + /** + * A comparator that implements the natural ordering of a group of + * mutually comparable elements. May be used when a supplied + * comparator is null. To simplify code-sharing within underlying + * implementations, the compare method only declares type Object + * for its second argument. + * + * Arrays class implementor's note: It is an empirical matter + * whether ComparableTimSort offers any performance benefit over + * TimSort used with this comparator. If not, you are better off + * deleting or bypassing ComparableTimSort. There is currently no + * empirical case for separating them for parallel sorting, so all + * public Object parallelSort methods use the same comparator + * based implementation. + */ + static final class NaturalOrder implements Comparator { + @SuppressWarnings("unchecked") + public int compare(Object first, Object second) { + return ((Comparable)first).compareTo(second); + } + static final NaturalOrder INSTANCE = new NaturalOrder(); + } + + /** + * Checks that {@code fromIndex} and {@code toIndex} are in + * the range and throws an exception if they aren't. + */ + private static void rangeCheck(int arrayLength, int fromIndex, int toIndex) { + if (fromIndex > toIndex) { + throw new IllegalArgumentException( + "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); + } + if (fromIndex < 0) { + throw new ArrayIndexOutOfBoundsException(fromIndex); + } + if (toIndex > arrayLength) { + throw new ArrayIndexOutOfBoundsException(toIndex); + } + } + /* - * Sorting of primitive type arrays. + * Sorting methods. Note that all public "sort" methods take the + * same form: Performing argument checks if necessary, and then + * expanding arguments into those required for the internal + * implementation methods residing in other package-private + * classes (except for legacyMergeSort, included in this class). */ /** @@ -95,7 +139,7 @@ public class Arrays { * @param a the array to be sorted */ public static void sort(int[] a) { - DualPivotQuicksort.sort(a); + DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); } /** @@ -120,7 +164,7 @@ public class Arrays { */ public static void sort(int[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - DualPivotQuicksort.sort(a, fromIndex, toIndex - 1); + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); } /** @@ -135,7 +179,7 @@ public class Arrays { * @param a the array to be sorted */ public static void sort(long[] a) { - DualPivotQuicksort.sort(a); + DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); } /** @@ -160,7 +204,7 @@ public class Arrays { */ public static void sort(long[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - DualPivotQuicksort.sort(a, fromIndex, toIndex - 1); + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); } /** @@ -175,7 +219,7 @@ public class Arrays { * @param a the array to be sorted */ public static void sort(short[] a) { - DualPivotQuicksort.sort(a); + DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); } /** @@ -200,7 +244,7 @@ public class Arrays { */ public static void sort(short[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - DualPivotQuicksort.sort(a, fromIndex, toIndex - 1); + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); } /** @@ -215,7 +259,7 @@ public class Arrays { * @param a the array to be sorted */ public static void sort(char[] a) { - DualPivotQuicksort.sort(a); + DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); } /** @@ -240,7 +284,7 @@ public class Arrays { */ public static void sort(char[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - DualPivotQuicksort.sort(a, fromIndex, toIndex - 1); + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); } /** @@ -255,7 +299,7 @@ public class Arrays { * @param a the array to be sorted */ public static void sort(byte[] a) { - DualPivotQuicksort.sort(a); + DualPivotQuicksort.sort(a, 0, a.length - 1); } /** @@ -303,7 +347,7 @@ public class Arrays { * @param a the array to be sorted */ public static void sort(float[] a) { - DualPivotQuicksort.sort(a); + DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); } /** @@ -336,7 +380,7 @@ public class Arrays { */ public static void sort(float[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - DualPivotQuicksort.sort(a, fromIndex, toIndex - 1); + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); } /** @@ -359,7 +403,7 @@ public class Arrays { * @param a the array to be sorted */ public static void sort(double[] a) { - DualPivotQuicksort.sort(a); + DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); } /** @@ -392,622 +436,420 @@ public class Arrays { */ public static void sort(double[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - DualPivotQuicksort.sort(a, fromIndex, toIndex - 1); + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); } - /* - * Sorting of complex type arrays. - */ - /** - * Old merge sort implementation can be selected (for - * compatibility with broken comparators) using a system property. - * Cannot be a static boolean in the enclosing class due to - * circular dependencies. To be removed in a future release. + * Sorts the specified array into ascending numerical order. + * + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(byte[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(byte[]) Arrays.sort} method. The algorithm requires a + * working space no greater than the size of the original array. The + * {@link ForkJoinPool#commonPool() ForkJoin common pool} is used to + * execute any parallel tasks. + * + * @param a the array to be sorted + * + * @since 1.8 */ - static final class LegacyMergeSort { - private static final boolean userRequested = - java.security.AccessController.doPrivileged( - new sun.security.action.GetBooleanAction( - "java.util.Arrays.useLegacyMergeSort")).booleanValue(); + public static void parallelSort(byte[] a) { + int n = a.length, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, 0, n - 1); + else + new ArraysParallelSortHelpers.FJByte.Sorter + (null, a, new byte[n], 0, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); + } + + /** + * Sorts the specified range of the array into ascending numerical order. + * The range to be sorted extends from the index {@code fromIndex}, + * inclusive, to the index {@code toIndex}, exclusive. If + * {@code fromIndex == toIndex}, the range to be sorted is empty. + * + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(byte[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(byte[]) Arrays.sort} method. The algorithm requires a working + * space no greater than the size of the specified range of the original + * array. The {@link ForkJoinPool#commonPool() ForkJoin common pool} is + * used to execute any parallel tasks. + * + * @param a the array to be sorted + * @param fromIndex the index of the first element, inclusive, to be sorted + * @param toIndex the index of the last element, exclusive, to be sorted + * + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > a.length} + * + * @since 1.8 + */ + public static void parallelSort(byte[] a, int fromIndex, int toIndex) { + rangeCheck(a.length, fromIndex, toIndex); + int n = toIndex - fromIndex, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1); + else + new ArraysParallelSortHelpers.FJByte.Sorter + (null, a, new byte[n], fromIndex, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); } - /* - * If this platform has an optimizing VM, check whether ComparableTimSort - * offers any performance benefit over TimSort in conjunction with a - * comparator that returns: - * {@code ((Comparable)first).compareTo(Second)}. - * If not, you are better off deleting ComparableTimSort to - * eliminate the code duplication. In other words, the commented - * out code below is the preferable implementation for sorting - * arrays of Comparables if it offers sufficient performance. - */ - -// /** -// * A comparator that implements the natural ordering of a group of -// * mutually comparable elements. Using this comparator saves us -// * from duplicating most of the code in this file (one version for -// * Comparables, one for explicit Comparators). -// */ -// private static final Comparator NATURAL_ORDER = -// new Comparator() { -// @SuppressWarnings("unchecked") -// public int compare(Object first, Object second) { -// return ((Comparable)first).compareTo(second); -// } -// }; -// -// public static void sort(Object[] a) { -// sort(a, 0, a.length, NATURAL_ORDER); -// } -// -// public static void sort(Object[] a, int fromIndex, int toIndex) { -// sort(a, fromIndex, toIndex, NATURAL_ORDER); -// } - /** - * Sorts the specified array of objects into ascending order, according - * to the {@linkplain Comparable natural ordering} of its elements. - * All elements in the array must implement the {@link Comparable} - * interface. Furthermore, all elements in the array must be - * mutually comparable (that is, {@code e1.compareTo(e2)} must - * not throw a {@code ClassCastException} for any elements {@code e1} - * and {@code e2} in the array). - * - *

This sort is guaranteed to be stable: equal elements will - * not be reordered as a result of the sort. + * Sorts the specified array into ascending numerical order. * - *

Implementation note: This implementation is a stable, adaptive, - * iterative mergesort that requires far fewer than n lg(n) comparisons - * when the input array is partially sorted, while offering the - * performance of a traditional mergesort when the input array is - * randomly ordered. If the input array is nearly sorted, the - * implementation requires approximately n comparisons. Temporary - * storage requirements vary from a small constant for nearly sorted - * input arrays to n/2 object references for randomly ordered input - * arrays. + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(char[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(char[]) Arrays.sort} method. The algorithm requires a + * working space no greater than the size of the original array. The + * {@link ForkJoinPool#commonPool() ForkJoin common pool} is used to + * execute any parallel tasks. * - *

The implementation takes equal advantage of ascending and - * descending order in its input array, and can take advantage of - * ascending and descending order in different parts of the the same - * input array. It is well-suited to merging two or more sorted arrays: - * simply concatenate the arrays and sort the resulting array. + * @param a the array to be sorted * - *

The implementation was adapted from Tim Peters's list sort for Python - * ( - * TimSort). It uses techiques from Peter McIlroy's "Optimistic - * Sorting and Information Theoretic Complexity", in Proceedings of the - * Fourth Annual ACM-SIAM Symposium on Discrete Algorithms, pp 467-474, - * January 1993. + * @since 1.8 + */ + public static void parallelSort(char[] a) { + int n = a.length, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0); + else + new ArraysParallelSortHelpers.FJChar.Sorter + (null, a, new char[n], 0, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); + } + + /** + * Sorts the specified range of the array into ascending numerical order. + * The range to be sorted extends from the index {@code fromIndex}, + * inclusive, to the index {@code toIndex}, exclusive. If + * {@code fromIndex == toIndex}, the range to be sorted is empty. + * + @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(char[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(char[]) Arrays.sort} method. The algorithm requires a working + * space no greater than the size of the specified range of the original + * array. The {@link ForkJoinPool#commonPool() ForkJoin common pool} is + * used to execute any parallel tasks. * * @param a the array to be sorted - * @throws ClassCastException if the array contains elements that are not - * mutually comparable (for example, strings and integers) - * @throws IllegalArgumentException (optional) if the natural - * ordering of the array elements is found to violate the - * {@link Comparable} contract + * @param fromIndex the index of the first element, inclusive, to be sorted + * @param toIndex the index of the last element, exclusive, to be sorted + * + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > a.length} + * + * @since 1.8 */ - public static void sort(Object[] a) { - if (LegacyMergeSort.userRequested) - legacyMergeSort(a); + public static void parallelSort(char[] a, int fromIndex, int toIndex) { + rangeCheck(a.length, fromIndex, toIndex); + int n = toIndex - fromIndex, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); else - ComparableTimSort.sort(a); - } - - /** To be removed in a future release. */ - private static void legacyMergeSort(Object[] a) { - Object[] aux = a.clone(); - mergeSort(aux, a, 0, a.length, 0); + new ArraysParallelSortHelpers.FJChar.Sorter + (null, a, new char[n], fromIndex, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); } /** - * Sorts the specified range of the specified array of objects into - * ascending order, according to the - * {@linkplain Comparable natural ordering} of its - * elements. The range to be sorted extends from index - * {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive. - * (If {@code fromIndex==toIndex}, the range to be sorted is empty.) All - * elements in this range must implement the {@link Comparable} - * interface. Furthermore, all elements in this range must be mutually - * comparable (that is, {@code e1.compareTo(e2)} must not throw a - * {@code ClassCastException} for any elements {@code e1} and - * {@code e2} in the array). - * - *

This sort is guaranteed to be stable: equal elements will - * not be reordered as a result of the sort. + * Sorts the specified array into ascending numerical order. * - *

Implementation note: This implementation is a stable, adaptive, - * iterative mergesort that requires far fewer than n lg(n) comparisons - * when the input array is partially sorted, while offering the - * performance of a traditional mergesort when the input array is - * randomly ordered. If the input array is nearly sorted, the - * implementation requires approximately n comparisons. Temporary - * storage requirements vary from a small constant for nearly sorted - * input arrays to n/2 object references for randomly ordered input - * arrays. + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(short[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(short[]) Arrays.sort} method. The algorithm requires a + * working space no greater than the size of the original array. The + * {@link ForkJoinPool#commonPool() ForkJoin common pool} is used to + * execute any parallel tasks. * - *

The implementation takes equal advantage of ascending and - * descending order in its input array, and can take advantage of - * ascending and descending order in different parts of the the same - * input array. It is well-suited to merging two or more sorted arrays: - * simply concatenate the arrays and sort the resulting array. + * @param a the array to be sorted * - *

The implementation was adapted from Tim Peters's list sort for Python - * ( - * TimSort). It uses techiques from Peter McIlroy's "Optimistic - * Sorting and Information Theoretic Complexity", in Proceedings of the - * Fourth Annual ACM-SIAM Symposium on Discrete Algorithms, pp 467-474, - * January 1993. + * @since 1.8 + */ + public static void parallelSort(short[] a) { + int n = a.length, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0); + else + new ArraysParallelSortHelpers.FJShort.Sorter + (null, a, new short[n], 0, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); + } + + /** + * Sorts the specified range of the array into ascending numerical order. + * The range to be sorted extends from the index {@code fromIndex}, + * inclusive, to the index {@code toIndex}, exclusive. If + * {@code fromIndex == toIndex}, the range to be sorted is empty. + * + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(short[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(short[]) Arrays.sort} method. The algorithm requires a working + * space no greater than the size of the specified range of the original + * array. The {@link ForkJoinPool#commonPool() ForkJoin common pool} is + * used to execute any parallel tasks. * * @param a the array to be sorted - * @param fromIndex the index of the first element (inclusive) to be - * sorted - * @param toIndex the index of the last element (exclusive) to be sorted - * @throws IllegalArgumentException if {@code fromIndex > toIndex} or - * (optional) if the natural ordering of the array elements is - * found to violate the {@link Comparable} contract - * @throws ArrayIndexOutOfBoundsException if {@code fromIndex < 0} or - * {@code toIndex > a.length} - * @throws ClassCastException if the array contains elements that are - * not mutually comparable (for example, strings and - * integers). + * @param fromIndex the index of the first element, inclusive, to be sorted + * @param toIndex the index of the last element, exclusive, to be sorted + * + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > a.length} + * + * @since 1.8 */ - public static void sort(Object[] a, int fromIndex, int toIndex) { - if (LegacyMergeSort.userRequested) - legacyMergeSort(a, fromIndex, toIndex); + public static void parallelSort(short[] a, int fromIndex, int toIndex) { + rangeCheck(a.length, fromIndex, toIndex); + int n = toIndex - fromIndex, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); else - ComparableTimSort.sort(a, fromIndex, toIndex); + new ArraysParallelSortHelpers.FJShort.Sorter + (null, a, new short[n], fromIndex, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); } - /** To be removed in a future release. */ - private static void legacyMergeSort(Object[] a, - int fromIndex, int toIndex) { + /** + * Sorts the specified array into ascending numerical order. + * + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(int[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(int[]) Arrays.sort} method. The algorithm requires a + * working space no greater than the size of the original array. The + * {@link ForkJoinPool#commonPool() ForkJoin common pool} is used to + * execute any parallel tasks. + * + * @param a the array to be sorted + * + * @since 1.8 + */ + public static void parallelSort(int[] a) { + int n = a.length, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0); + else + new ArraysParallelSortHelpers.FJInt.Sorter + (null, a, new int[n], 0, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); + } + + /** + * Sorts the specified range of the array into ascending numerical order. + * The range to be sorted extends from the index {@code fromIndex}, + * inclusive, to the index {@code toIndex}, exclusive. If + * {@code fromIndex == toIndex}, the range to be sorted is empty. + * + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(int[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(int[]) Arrays.sort} method. The algorithm requires a working + * space no greater than the size of the specified range of the original + * array. The {@link ForkJoinPool#commonPool() ForkJoin common pool} is + * used to execute any parallel tasks. + * + * @param a the array to be sorted + * @param fromIndex the index of the first element, inclusive, to be sorted + * @param toIndex the index of the last element, exclusive, to be sorted + * + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > a.length} + * + * @since 1.8 + */ + public static void parallelSort(int[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - Object[] aux = copyOfRange(a, fromIndex, toIndex); - mergeSort(aux, a, fromIndex, toIndex, -fromIndex); + int n = toIndex - fromIndex, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); + else + new ArraysParallelSortHelpers.FJInt.Sorter + (null, a, new int[n], fromIndex, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); } /** - * Tuning parameter: list size at or below which insertion sort will be - * used in preference to mergesort. - * To be removed in a future release. + * Sorts the specified array into ascending numerical order. + * + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(long[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(long[]) Arrays.sort} method. The algorithm requires a + * working space no greater than the size of the original array. The + * {@link ForkJoinPool#commonPool() ForkJoin common pool} is used to + * execute any parallel tasks. + * + * @param a the array to be sorted + * + * @since 1.8 */ - private static final int INSERTIONSORT_THRESHOLD = 7; - - /** - * Src is the source array that starts at index 0 - * Dest is the (possibly larger) array destination with a possible offset - * low is the index in dest to start sorting - * high is the end index in dest to end sorting - * off is the offset to generate corresponding low, high in src - * To be removed in a future release. - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - private static void mergeSort(Object[] src, - Object[] dest, - int low, - int high, - int off) { - int length = high - low; - - // Insertion sort on smallest arrays - if (length < INSERTIONSORT_THRESHOLD) { - for (int i=low; ilow && - ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--) - swap(dest, j, j-1); - return; - } - - // Recursively sort halves of dest into src - int destLow = low; - int destHigh = high; - low += off; - high += off; - int mid = (low + high) >>> 1; - mergeSort(dest, src, low, mid, -off); - mergeSort(dest, src, mid, high, -off); - - // If list is already sorted, just copy from src to dest. This is an - // optimization that results in faster sorts for nearly ordered lists. - if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) { - System.arraycopy(src, low, dest, destLow, length); - return; - } - - // Merge sorted halves (now in src) into dest - for(int i = destLow, p = low, q = mid; i < destHigh; i++) { - if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0) - dest[i] = src[p++]; - else - dest[i] = src[q++]; - } - } - - /** - * Swaps x[a] with x[b]. - */ - private static void swap(Object[] x, int a, int b) { - Object t = x[a]; - x[a] = x[b]; - x[b] = t; - } - - /** - * Sorts the specified array of objects according to the order induced by - * the specified comparator. All elements in the array must be - * mutually comparable by the specified comparator (that is, - * {@code c.compare(e1, e2)} must not throw a {@code ClassCastException} - * for any elements {@code e1} and {@code e2} in the array). - * - *

This sort is guaranteed to be stable: equal elements will - * not be reordered as a result of the sort. - * - *

Implementation note: This implementation is a stable, adaptive, - * iterative mergesort that requires far fewer than n lg(n) comparisons - * when the input array is partially sorted, while offering the - * performance of a traditional mergesort when the input array is - * randomly ordered. If the input array is nearly sorted, the - * implementation requires approximately n comparisons. Temporary - * storage requirements vary from a small constant for nearly sorted - * input arrays to n/2 object references for randomly ordered input - * arrays. - * - *

The implementation takes equal advantage of ascending and - * descending order in its input array, and can take advantage of - * ascending and descending order in different parts of the the same - * input array. It is well-suited to merging two or more sorted arrays: - * simply concatenate the arrays and sort the resulting array. - * - *

The implementation was adapted from Tim Peters's list sort for Python - * ( - * TimSort). It uses techiques from Peter McIlroy's "Optimistic - * Sorting and Information Theoretic Complexity", in Proceedings of the - * Fourth Annual ACM-SIAM Symposium on Discrete Algorithms, pp 467-474, - * January 1993. - * - * @param a the array to be sorted - * @param c the comparator to determine the order of the array. A - * {@code null} value indicates that the elements' - * {@linkplain Comparable natural ordering} should be used. - * @throws ClassCastException if the array contains elements that are - * not mutually comparable using the specified comparator - * @throws IllegalArgumentException (optional) if the comparator is - * found to violate the {@link Comparator} contract - */ - public static void sort(T[] a, Comparator c) { - if (LegacyMergeSort.userRequested) - legacyMergeSort(a, c); - else - TimSort.sort(a, c); - } - - /** To be removed in a future release. */ - private static void legacyMergeSort(T[] a, Comparator c) { - T[] aux = a.clone(); - if (c==null) - mergeSort(aux, a, 0, a.length, 0); - else - mergeSort(aux, a, 0, a.length, 0, c); - } - - /** - * Sorts the specified range of the specified array of objects according - * to the order induced by the specified comparator. The range to be - * sorted extends from index {@code fromIndex}, inclusive, to index - * {@code toIndex}, exclusive. (If {@code fromIndex==toIndex}, the - * range to be sorted is empty.) All elements in the range must be - * mutually comparable by the specified comparator (that is, - * {@code c.compare(e1, e2)} must not throw a {@code ClassCastException} - * for any elements {@code e1} and {@code e2} in the range). - * - *

This sort is guaranteed to be stable: equal elements will - * not be reordered as a result of the sort. - * - *

Implementation note: This implementation is a stable, adaptive, - * iterative mergesort that requires far fewer than n lg(n) comparisons - * when the input array is partially sorted, while offering the - * performance of a traditional mergesort when the input array is - * randomly ordered. If the input array is nearly sorted, the - * implementation requires approximately n comparisons. Temporary - * storage requirements vary from a small constant for nearly sorted - * input arrays to n/2 object references for randomly ordered input - * arrays. - * - *

The implementation takes equal advantage of ascending and - * descending order in its input array, and can take advantage of - * ascending and descending order in different parts of the the same - * input array. It is well-suited to merging two or more sorted arrays: - * simply concatenate the arrays and sort the resulting array. - * - *

The implementation was adapted from Tim Peters's list sort for Python - * ( - * TimSort). It uses techiques from Peter McIlroy's "Optimistic - * Sorting and Information Theoretic Complexity", in Proceedings of the - * Fourth Annual ACM-SIAM Symposium on Discrete Algorithms, pp 467-474, - * January 1993. - * - * @param a the array to be sorted - * @param fromIndex the index of the first element (inclusive) to be - * sorted - * @param toIndex the index of the last element (exclusive) to be sorted - * @param c the comparator to determine the order of the array. A - * {@code null} value indicates that the elements' - * {@linkplain Comparable natural ordering} should be used. - * @throws ClassCastException if the array contains elements that are not - * mutually comparable using the specified comparator. - * @throws IllegalArgumentException if {@code fromIndex > toIndex} or - * (optional) if the comparator is found to violate the - * {@link Comparator} contract - * @throws ArrayIndexOutOfBoundsException if {@code fromIndex < 0} or - * {@code toIndex > a.length} - */ - public static void sort(T[] a, int fromIndex, int toIndex, - Comparator c) { - if (LegacyMergeSort.userRequested) - legacyMergeSort(a, fromIndex, toIndex, c); - else - TimSort.sort(a, fromIndex, toIndex, c); - } - - /** To be removed in a future release. */ - private static void legacyMergeSort(T[] a, int fromIndex, int toIndex, - Comparator c) { - rangeCheck(a.length, fromIndex, toIndex); - T[] aux = copyOfRange(a, fromIndex, toIndex); - if (c==null) - mergeSort(aux, a, fromIndex, toIndex, -fromIndex); - else - mergeSort(aux, a, fromIndex, toIndex, -fromIndex, c); - } - - /** - * Src is the source array that starts at index 0 - * Dest is the (possibly larger) array destination with a possible offset - * low is the index in dest to start sorting - * high is the end index in dest to end sorting - * off is the offset into src corresponding to low in dest - * To be removed in a future release. - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - private static void mergeSort(Object[] src, - Object[] dest, - int low, int high, int off, - Comparator c) { - int length = high - low; - - // Insertion sort on smallest arrays - if (length < INSERTIONSORT_THRESHOLD) { - for (int i=low; ilow && c.compare(dest[j-1], dest[j])>0; j--) - swap(dest, j, j-1); - return; - } - - // Recursively sort halves of dest into src - int destLow = low; - int destHigh = high; - low += off; - high += off; - int mid = (low + high) >>> 1; - mergeSort(dest, src, low, mid, -off, c); - mergeSort(dest, src, mid, high, -off, c); - - // If list is already sorted, just copy from src to dest. This is an - // optimization that results in faster sorts for nearly ordered lists. - if (c.compare(src[mid-1], src[mid]) <= 0) { - System.arraycopy(src, low, dest, destLow, length); - return; - } - - // Merge sorted halves (now in src) into dest - for(int i = destLow, p = low, q = mid; i < destHigh; i++) { - if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0) - dest[i] = src[p++]; - else - dest[i] = src[q++]; - } - } - - /* - * Parallel sorting of primitive type arrays. - */ - - /** - * Sorts the specified array into ascending numerical order. - * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(byte[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. - * - * @param a the array to be sorted - * - * @since 1.8 - */ - public static void parallelSort(byte[] a) { - parallelSort(a, 0, a.length); - } - - /** - * Sorts the specified range of the array into ascending order. The range - * to be sorted extends from the index {@code fromIndex}, inclusive, to - * the index {@code toIndex}, exclusive. If {@code fromIndex == toIndex}, - * the range to be sorted is empty. - * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(byte[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. - * - * @param a the array to be sorted - * @param fromIndex the index of the first element, inclusive, to be sorted - * @param toIndex the index of the last element, exclusive, to be sorted - * - * @throws IllegalArgumentException if {@code fromIndex > toIndex} - * @throws ArrayIndexOutOfBoundsException - * if {@code fromIndex < 0} or {@code toIndex > a.length} - * - * @since 1.8 - */ - public static void parallelSort(byte[] a, int fromIndex, int toIndex) { - rangeCheck(a.length, fromIndex, toIndex); - int nelements = toIndex - fromIndex; - int gran = getSplitThreshold(nelements); - FJByte.Sorter task = new FJByte.Sorter(a, new byte[a.length], fromIndex, - nelements, gran); - task.invoke(); - } - - /** - * Sorts the specified array into ascending numerical order. - * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(char[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. - * - * @param a the array to be sorted - * - * @since 1.8 - */ - public static void parallelSort(char[] a) { - parallelSort(a, 0, a.length); - } - - /** - * Sorts the specified range of the array into ascending order. The range - * to be sorted extends from the index {@code fromIndex}, inclusive, to - * the index {@code toIndex}, exclusive. If {@code fromIndex == toIndex}, - * the range to be sorted is empty. - * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(char[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. - * - * @param a the array to be sorted - * @param fromIndex the index of the first element, inclusive, to be sorted - * @param toIndex the index of the last element, exclusive, to be sorted - * - * @throws IllegalArgumentException if {@code fromIndex > toIndex} - * @throws ArrayIndexOutOfBoundsException - * if {@code fromIndex < 0} or {@code toIndex > a.length} - * - * @since 1.8 - */ - public static void parallelSort(char[] a, int fromIndex, int toIndex) { - rangeCheck(a.length, fromIndex, toIndex); - int nelements = toIndex - fromIndex; - int gran = getSplitThreshold(nelements); - FJChar.Sorter task = new FJChar.Sorter(a, new char[a.length], fromIndex, - nelements, gran); - task.invoke(); - } - - /** - * Sorts the specified array into ascending numerical order. - * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(short[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. - * - * @param a the array to be sorted - * - * @since 1.8 - */ - public static void parallelSort(short[] a) { - parallelSort(a, 0, a.length); - } - - /** - * Sorts the specified range of the array into ascending order. The range - * to be sorted extends from the index {@code fromIndex}, inclusive, to - * the index {@code toIndex}, exclusive. If {@code fromIndex == toIndex}, - * the range to be sorted is empty. - * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(short[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. - * - * @param a the array to be sorted - * @param fromIndex the index of the first element, inclusive, to be sorted - * @param toIndex the index of the last element, exclusive, to be sorted - * - * @throws IllegalArgumentException if {@code fromIndex > toIndex} - * @throws ArrayIndexOutOfBoundsException - * if {@code fromIndex < 0} or {@code toIndex > a.length} - * - * @since 1.8 - */ - public static void parallelSort(short[] a, int fromIndex, int toIndex) { - rangeCheck(a.length, fromIndex, toIndex); - int nelements = toIndex - fromIndex; - int gran = getSplitThreshold(nelements); - FJShort.Sorter task = new FJShort.Sorter(a, new short[a.length], fromIndex, - nelements, gran); - task.invoke(); - } + public static void parallelSort(long[] a) { + int n = a.length, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0); + else + new ArraysParallelSortHelpers.FJLong.Sorter + (null, a, new long[n], 0, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); + } + + /** + * Sorts the specified range of the array into ascending numerical order. + * The range to be sorted extends from the index {@code fromIndex}, + * inclusive, to the index {@code toIndex}, exclusive. If + * {@code fromIndex == toIndex}, the range to be sorted is empty. + * + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(long[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(long[]) Arrays.sort} method. The algorithm requires a working + * space no greater than the size of the specified range of the original + * array. The {@link ForkJoinPool#commonPool() ForkJoin common pool} is + * used to execute any parallel tasks. + * + * @param a the array to be sorted + * @param fromIndex the index of the first element, inclusive, to be sorted + * @param toIndex the index of the last element, exclusive, to be sorted + * + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > a.length} + * + * @since 1.8 + */ + public static void parallelSort(long[] a, int fromIndex, int toIndex) { + rangeCheck(a.length, fromIndex, toIndex); + int n = toIndex - fromIndex, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); + else + new ArraysParallelSortHelpers.FJLong.Sorter + (null, a, new long[n], fromIndex, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); + } /** * Sorts the specified array into ascending numerical order. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(int[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. + *

The {@code <} relation does not provide a total order on all float + * values: {@code -0.0f == 0.0f} is {@code true} and a {@code Float.NaN} + * value compares neither less than, greater than, nor equal to any value, + * even itself. This method uses the total order imposed by the method + * {@link Float#compareTo}: {@code -0.0f} is treated as less than value + * {@code 0.0f} and {@code Float.NaN} is considered greater than any + * other value and all {@code Float.NaN} values are considered equal. + * + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(float[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(float[]) Arrays.sort} method. The algorithm requires a + * working space no greater than the size of the original array. The + * {@link ForkJoinPool#commonPool() ForkJoin common pool} is used to + * execute any parallel tasks. * * @param a the array to be sorted * * @since 1.8 */ - public static void parallelSort(int[] a) { - parallelSort(a, 0, a.length); + public static void parallelSort(float[] a) { + int n = a.length, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0); + else + new ArraysParallelSortHelpers.FJFloat.Sorter + (null, a, new float[n], 0, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); } /** - * Sorts the specified range of the array into ascending order. The range - * to be sorted extends from the index {@code fromIndex}, inclusive, to - * the index {@code toIndex}, exclusive. If {@code fromIndex == toIndex}, - * the range to be sorted is empty. + * Sorts the specified range of the array into ascending numerical order. + * The range to be sorted extends from the index {@code fromIndex}, + * inclusive, to the index {@code toIndex}, exclusive. If + * {@code fromIndex == toIndex}, the range to be sorted is empty. + * + *

The {@code <} relation does not provide a total order on all float + * values: {@code -0.0f == 0.0f} is {@code true} and a {@code Float.NaN} + * value compares neither less than, greater than, nor equal to any value, + * even itself. This method uses the total order imposed by the method + * {@link Float#compareTo}: {@code -0.0f} is treated as less than value + * {@code 0.0f} and {@code Float.NaN} is considered greater than any + * other value and all {@code Float.NaN} values are considered equal. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(int[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(float[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(float[]) Arrays.sort} method. The algorithm requires a working + * space no greater than the size of the specified range of the original + * array. The {@link ForkJoinPool#commonPool() ForkJoin common pool} is * used to execute any parallel tasks. * * @param a the array to be sorted @@ -1020,48 +862,80 @@ public class Arrays { * * @since 1.8 */ - public static void parallelSort(int[] a, int fromIndex, int toIndex) { + public static void parallelSort(float[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - int nelements = toIndex - fromIndex; - int gran = getSplitThreshold(nelements); - FJInt.Sorter task = new FJInt.Sorter(a, new int[a.length], fromIndex, - nelements, gran); - task.invoke(); + int n = toIndex - fromIndex, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); + else + new ArraysParallelSortHelpers.FJFloat.Sorter + (null, a, new float[n], fromIndex, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); } /** * Sorts the specified array into ascending numerical order. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(long[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. + *

The {@code <} relation does not provide a total order on all double + * values: {@code -0.0d == 0.0d} is {@code true} and a {@code Double.NaN} + * value compares neither less than, greater than, nor equal to any value, + * even itself. This method uses the total order imposed by the method + * {@link Double#compareTo}: {@code -0.0d} is treated as less than value + * {@code 0.0d} and {@code Double.NaN} is considered greater than any + * other value and all {@code Double.NaN} values are considered equal. + * + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(double[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(double[]) Arrays.sort} method. The algorithm requires a + * working space no greater than the size of the original array. The + * {@link ForkJoinPool#commonPool() ForkJoin common pool} is used to + * execute any parallel tasks. * * @param a the array to be sorted * * @since 1.8 */ - public static void parallelSort(long[] a) { - parallelSort(a, 0, a.length); + public static void parallelSort(double[] a) { + int n = a.length, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0); + else + new ArraysParallelSortHelpers.FJDouble.Sorter + (null, a, new double[n], 0, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); } /** - * Sorts the specified range of the array into ascending order. The range - * to be sorted extends from the index {@code fromIndex}, inclusive, to - * the index {@code toIndex}, exclusive. If {@code fromIndex == toIndex}, - * the range to be sorted is empty. + * Sorts the specified range of the array into ascending numerical order. + * The range to be sorted extends from the index {@code fromIndex}, + * inclusive, to the index {@code toIndex}, exclusive. If + * {@code fromIndex == toIndex}, the range to be sorted is empty. + * + *

The {@code <} relation does not provide a total order on all double + * values: {@code -0.0d == 0.0d} is {@code true} and a {@code Double.NaN} + * value compares neither less than, greater than, nor equal to any value, + * even itself. This method uses the total order imposed by the method + * {@link Double#compareTo}: {@code -0.0d} is treated as less than value + * {@code 0.0d} and {@code Double.NaN} is considered greater than any + * other value and all {@code Double.NaN} values are considered equal. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(long[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(double[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(double[]) Arrays.sort} method. The algorithm requires a working + * space no greater than the size of the specified range of the original + * array. The {@link ForkJoinPool#commonPool() ForkJoin common pool} is * used to execute any parallel tasks. * * @param a the array to be sorted @@ -1074,158 +948,248 @@ public class Arrays { * * @since 1.8 */ - public static void parallelSort(long[] a, int fromIndex, int toIndex) { + public static void parallelSort(double[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - int nelements = toIndex - fromIndex; - int gran = getSplitThreshold(nelements); - FJLong.Sorter task = new FJLong.Sorter(a, new long[a.length], fromIndex, - nelements, gran); - task.invoke(); + int n = toIndex - fromIndex, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0); + else + new ArraysParallelSortHelpers.FJDouble.Sorter + (null, a, new double[n], fromIndex, n, 0, + ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g).invoke(); } /** - * Sorts the specified array into ascending numerical order. + * Sorts the specified array of objects into ascending order, according + * to the {@linkplain Comparable natural ordering} of its elements. + * All elements in the array must implement the {@link Comparable} + * interface. Furthermore, all elements in the array must be + * mutually comparable (that is, {@code e1.compareTo(e2)} must + * not throw a {@code ClassCastException} for any elements {@code e1} + * and {@code e2} in the array). * - *

The {@code <} relation does not provide a total order on all float - * values: {@code -0.0f == 0.0f} is {@code true} and a {@code Float.NaN} - * value compares neither less than, greater than, nor equal to any value, - * even itself. This method uses the total order imposed by the method - * {@link Float#compareTo}: {@code -0.0f} is treated as less than value - * {@code 0.0f} and {@code Float.NaN} is considered greater than any - * other value and all {@code Float.NaN} values are considered equal. + *

This sort is guaranteed to be stable: equal elements will + * not be reordered as a result of the sort. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(float[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(Object[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(Object[]) Arrays.sort} method. The algorithm requires a + * working space no greater than the size of the original array. The + * {@link ForkJoinPool#commonPool() ForkJoin common pool} is used to + * execute any parallel tasks. * * @param a the array to be sorted * + * @throws ClassCastException if the array contains elements that are not + * mutually comparable (for example, strings and integers) + * @throws IllegalArgumentException (optional) if the natural + * ordering of the array elements is found to violate the + * {@link Comparable} contract + * * @since 1.8 */ - public static void parallelSort(float[] a) { - parallelSort(a, 0, a.length); + @SuppressWarnings("unchecked") + public static > void parallelSort(T[] a) { + int n = a.length, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + TimSort.sort(a, 0, n, NaturalOrder.INSTANCE, null, 0, 0); + else + new ArraysParallelSortHelpers.FJObject.Sorter + (null, a, + (T[])Array.newInstance(a.getClass().getComponentType(), n), + 0, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g, NaturalOrder.INSTANCE).invoke(); } /** - * Sorts the specified range of the array into ascending order. The range - * to be sorted extends from the index {@code fromIndex}, inclusive, to - * the index {@code toIndex}, exclusive. If {@code fromIndex == toIndex}, - * the range to be sorted is empty. + * Sorts the specified range of the specified array of objects into + * ascending order, according to the + * {@linkplain Comparable natural ordering} of its + * elements. The range to be sorted extends from index + * {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive. + * (If {@code fromIndex==toIndex}, the range to be sorted is empty.) All + * elements in this range must implement the {@link Comparable} + * interface. Furthermore, all elements in this range must be mutually + * comparable (that is, {@code e1.compareTo(e2)} must not throw a + * {@code ClassCastException} for any elements {@code e1} and + * {@code e2} in the array). * - *

The {@code <} relation does not provide a total order on all float - * values: {@code -0.0f == 0.0f} is {@code true} and a {@code Float.NaN} - * value compares neither less than, greater than, nor equal to any value, - * even itself. This method uses the total order imposed by the method - * {@link Float#compareTo}: {@code -0.0f} is treated as less than value - * {@code 0.0f} and {@code Float.NaN} is considered greater than any - * other value and all {@code Float.NaN} values are considered equal. + *

This sort is guaranteed to be stable: equal elements will + * not be reordered as a result of the sort. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(float[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(Object[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(Object[]) Arrays.sort} method. The algorithm requires a working + * space no greater than the size of the specified range of the original + * array. The {@link ForkJoinPool#commonPool() ForkJoin common pool} is * used to execute any parallel tasks. * * @param a the array to be sorted - * @param fromIndex the index of the first element, inclusive, to be sorted - * @param toIndex the index of the last element, exclusive, to be sorted - * - * @throws IllegalArgumentException if {@code fromIndex > toIndex} - * @throws ArrayIndexOutOfBoundsException - * if {@code fromIndex < 0} or {@code toIndex > a.length} + * @param fromIndex the index of the first element (inclusive) to be + * sorted + * @param toIndex the index of the last element (exclusive) to be sorted + * @throws IllegalArgumentException if {@code fromIndex > toIndex} or + * (optional) if the natural ordering of the array elements is + * found to violate the {@link Comparable} contract + * @throws ArrayIndexOutOfBoundsException if {@code fromIndex < 0} or + * {@code toIndex > a.length} + * @throws ClassCastException if the array contains elements that are + * not mutually comparable (for example, strings and + * integers). * * @since 1.8 */ - public static void parallelSort(float[] a, int fromIndex, int toIndex) { + @SuppressWarnings("unchecked") + public static > + void parallelSort(T[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - int nelements = toIndex - fromIndex; - int gran = getSplitThreshold(nelements); - FJFloat.Sorter task = new FJFloat.Sorter(a, new float[a.length], fromIndex, - nelements, gran); - task.invoke(); + int n = toIndex - fromIndex, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + TimSort.sort(a, fromIndex, toIndex, NaturalOrder.INSTANCE, null, 0, 0); + else + new ArraysParallelSortHelpers.FJObject.Sorter + (null, a, + (T[])Array.newInstance(a.getClass().getComponentType(), n), + fromIndex, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g, NaturalOrder.INSTANCE).invoke(); } /** - * Sorts the specified array into ascending numerical order. + * Sorts the specified array of objects according to the order induced by + * the specified comparator. All elements in the array must be + * mutually comparable by the specified comparator (that is, + * {@code c.compare(e1, e2)} must not throw a {@code ClassCastException} + * for any elements {@code e1} and {@code e2} in the array). * - *

The {@code <} relation does not provide a total order on all double - * values: {@code -0.0d == 0.0d} is {@code true} and a {@code Double.NaN} - * value compares neither less than, greater than, nor equal to any value, - * even itself. This method uses the total order imposed by the method - * {@link Double#compareTo}: {@code -0.0d} is treated as less than value - * {@code 0.0d} and {@code Double.NaN} is considered greater than any - * other value and all {@code Double.NaN} values are considered equal. + *

This sort is guaranteed to be stable: equal elements will + * not be reordered as a result of the sort. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(double[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(Object[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(Object[]) Arrays.sort} method. The algorithm requires a + * working space no greater than the size of the original array. The + * {@link ForkJoinPool#commonPool() ForkJoin common pool} is used to + * execute any parallel tasks. * * @param a the array to be sorted + * @param cmp the comparator to determine the order of the array. A + * {@code null} value indicates that the elements' + * {@linkplain Comparable natural ordering} should be used. + * @throws ClassCastException if the array contains elements that are + * not mutually comparable using the specified comparator + * @throws IllegalArgumentException (optional) if the comparator is + * found to violate the {@link java.util.Comparator} contract * * @since 1.8 */ - public static void parallelSort(double[] a) { - parallelSort(a, 0, a.length); + @SuppressWarnings("unchecked") + public static void parallelSort(T[] a, Comparator cmp) { + if (cmp == null) + cmp = NaturalOrder.INSTANCE; + int n = a.length, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + TimSort.sort(a, 0, n, cmp, null, 0, 0); + else + new ArraysParallelSortHelpers.FJObject.Sorter + (null, a, + (T[])Array.newInstance(a.getClass().getComponentType(), n), + 0, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g, cmp).invoke(); } /** - * Sorts the specified range of the array into ascending order. The range - * to be sorted extends from the index {@code fromIndex}, inclusive, to - * the index {@code toIndex}, exclusive. If {@code fromIndex == toIndex}, - * the range to be sorted is empty. + * Sorts the specified range of the specified array of objects according + * to the order induced by the specified comparator. The range to be + * sorted extends from index {@code fromIndex}, inclusive, to index + * {@code toIndex}, exclusive. (If {@code fromIndex==toIndex}, the + * range to be sorted is empty.) All elements in the range must be + * mutually comparable by the specified comparator (that is, + * {@code c.compare(e1, e2)} must not throw a {@code ClassCastException} + * for any elements {@code e1} and {@code e2} in the range). * - *

The {@code <} relation does not provide a total order on all double - * values: {@code -0.0d == 0.0d} is {@code true} and a {@code Double.NaN} - * value compares neither less than, greater than, nor equal to any value, - * even itself. This method uses the total order imposed by the method - * {@link Double#compareTo}: {@code -0.0d} is treated as less than value - * {@code 0.0d} and {@code Double.NaN} is considered greater than any - * other value and all {@code Double.NaN} values are considered equal. + *

This sort is guaranteed to be stable: equal elements will + * not be reordered as a result of the sort. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(double[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is + * @implNote The sorting algorithm is a parallel sort-merge that breaks the + * array into sub-arrays that are themselves sorted and then merged. When + * the sub-array length reaches a minimum granularity, the sub-array is + * sorted using the appropriate {@link Arrays#sort(Object[]) Arrays.sort} + * method. If the length of the specified array is less than the minimum + * granularity, then it is sorted using the appropriate {@link + * Arrays#sort(Object[]) Arrays.sort} method. The algorithm requires a working + * space no greater than the size of the specified range of the original + * array. The {@link ForkJoinPool#commonPool() ForkJoin common pool} is * used to execute any parallel tasks. * * @param a the array to be sorted - * @param fromIndex the index of the first element, inclusive, to be sorted - * @param toIndex the index of the last element, exclusive, to be sorted - * - * @throws IllegalArgumentException if {@code fromIndex > toIndex} - * @throws ArrayIndexOutOfBoundsException - * if {@code fromIndex < 0} or {@code toIndex > a.length} + * @param fromIndex the index of the first element (inclusive) to be + * sorted + * @param toIndex the index of the last element (exclusive) to be sorted + * @param cmp the comparator to determine the order of the array. A + * {@code null} value indicates that the elements' + * {@linkplain Comparable natural ordering} should be used. + * @throws IllegalArgumentException if {@code fromIndex > toIndex} or + * (optional) if the natural ordering of the array elements is + * found to violate the {@link Comparable} contract + * @throws ArrayIndexOutOfBoundsException if {@code fromIndex < 0} or + * {@code toIndex > a.length} + * @throws ClassCastException if the array contains elements that are + * not mutually comparable (for example, strings and + * integers). * * @since 1.8 */ - public static void parallelSort(double[] a, int fromIndex, int toIndex) { + @SuppressWarnings("unchecked") + public static void parallelSort(T[] a, int fromIndex, int toIndex, + Comparator cmp) { rangeCheck(a.length, fromIndex, toIndex); - int nelements = toIndex - fromIndex; - int gran = getSplitThreshold(nelements); - FJDouble.Sorter task = new FJDouble.Sorter(a, new double[a.length], - fromIndex, nelements, gran); - task.invoke(); + if (cmp == null) + cmp = NaturalOrder.INSTANCE; + int n = toIndex - fromIndex, p, g; + if (n <= MIN_ARRAY_SORT_GRAN || + (p = ForkJoinPool.getCommonPoolParallelism()) == 1) + TimSort.sort(a, fromIndex, toIndex, cmp, null, 0, 0); + else + new ArraysParallelSortHelpers.FJObject.Sorter + (null, a, + (T[])Array.newInstance(a.getClass().getComponentType(), n), + fromIndex, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? + MIN_ARRAY_SORT_GRAN : g, cmp).invoke(); } /* - * Parallel sorting of complex type arrays. + * Sorting of complex type arrays. + */ + + /** + * Old merge sort implementation can be selected (for + * compatibility with broken comparators) using a system property. + * Cannot be a static boolean in the enclosing class due to + * circular dependencies. To be removed in a future release. */ + static final class LegacyMergeSort { + private static final boolean userRequested = + java.security.AccessController.doPrivileged( + new sun.security.action.GetBooleanAction( + "java.util.Arrays.useLegacyMergeSort")).booleanValue(); + } /** * Sorts the specified array of objects into ascending order, according @@ -1236,30 +1200,50 @@ public class Arrays { * not throw a {@code ClassCastException} for any elements {@code e1} * and {@code e2} in the array). * - *

This sort is not guaranteed to be stable: equal elements - * may be reordered as a result of the sort. + *

This sort is guaranteed to be stable: equal elements will + * not be reordered as a result of the sort. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(Object[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. + *

Implementation note: This implementation is a stable, adaptive, + * iterative mergesort that requires far fewer than n lg(n) comparisons + * when the input array is partially sorted, while offering the + * performance of a traditional mergesort when the input array is + * randomly ordered. If the input array is nearly sorted, the + * implementation requires approximately n comparisons. Temporary + * storage requirements vary from a small constant for nearly sorted + * input arrays to n/2 object references for randomly ordered input + * arrays. * - * @param a the array to be sorted + *

The implementation takes equal advantage of ascending and + * descending order in its input array, and can take advantage of + * ascending and descending order in different parts of the the same + * input array. It is well-suited to merging two or more sorted arrays: + * simply concatenate the arrays and sort the resulting array. + * + *

The implementation was adapted from Tim Peters's list sort for Python + * ( + * TimSort). It uses techiques from Peter McIlroy's "Optimistic + * Sorting and Information Theoretic Complexity", in Proceedings of the + * Fourth Annual ACM-SIAM Symposium on Discrete Algorithms, pp 467-474, + * January 1993. * + * @param a the array to be sorted * @throws ClassCastException if the array contains elements that are not * mutually comparable (for example, strings and integers) * @throws IllegalArgumentException (optional) if the natural * ordering of the array elements is found to violate the * {@link Comparable} contract - * - * @since 1.8 */ - public static > void parallelSort(T[] a) { - parallelSort(a, 0, a.length); + public static void sort(Object[] a) { + if (LegacyMergeSort.userRequested) + legacyMergeSort(a); + else + ComparableTimSort.sort(a, 0, a.length, null, 0, 0); + } + + /** To be removed in a future release. */ + private static void legacyMergeSort(Object[] a) { + Object[] aux = a.clone(); + mergeSort(aux, a, 0, a.length, 0); } /** @@ -1275,17 +1259,31 @@ public class Arrays { * {@code ClassCastException} for any elements {@code e1} and * {@code e2} in the array). * - *

This sort is not guaranteed to be stable: equal elements - * may be reordered as a result of the sort. + *

This sort is guaranteed to be stable: equal elements will + * not be reordered as a result of the sort. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(Object[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. + *

Implementation note: This implementation is a stable, adaptive, + * iterative mergesort that requires far fewer than n lg(n) comparisons + * when the input array is partially sorted, while offering the + * performance of a traditional mergesort when the input array is + * randomly ordered. If the input array is nearly sorted, the + * implementation requires approximately n comparisons. Temporary + * storage requirements vary from a small constant for nearly sorted + * input arrays to n/2 object references for randomly ordered input + * arrays. + * + *

The implementation takes equal advantage of ascending and + * descending order in its input array, and can take advantage of + * ascending and descending order in different parts of the the same + * input array. It is well-suited to merging two or more sorted arrays: + * simply concatenate the arrays and sort the resulting array. + * + *

The implementation was adapted from Tim Peters's list sort for Python + * ( + * TimSort). It uses techiques from Peter McIlroy's "Optimistic + * Sorting and Information Theoretic Complexity", in Proceedings of the + * Fourth Annual ACM-SIAM Symposium on Discrete Algorithms, pp 467-474, + * January 1993. * * @param a the array to be sorted * @param fromIndex the index of the first element (inclusive) to be @@ -1299,21 +1297,86 @@ public class Arrays { * @throws ClassCastException if the array contains elements that are * not mutually comparable (for example, strings and * integers). - * - * @since 1.8 */ - public static > - void parallelSort(T[] a, int fromIndex, int toIndex) { + public static void sort(Object[] a, int fromIndex, int toIndex) { rangeCheck(a.length, fromIndex, toIndex); - int nelements = toIndex - fromIndex; - Class tc = a.getClass().getComponentType(); - @SuppressWarnings("unchecked") - T[] workspace = (T[])Array.newInstance(tc, a.length); - int gran = getSplitThreshold(nelements); - FJComparable.Sorter task = new FJComparable.Sorter<>(a, workspace, - fromIndex, - nelements, gran); - task.invoke(); + if (LegacyMergeSort.userRequested) + legacyMergeSort(a, fromIndex, toIndex); + else + ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0); + } + + /** To be removed in a future release. */ + private static void legacyMergeSort(Object[] a, + int fromIndex, int toIndex) { + Object[] aux = copyOfRange(a, fromIndex, toIndex); + mergeSort(aux, a, fromIndex, toIndex, -fromIndex); + } + + /** + * Tuning parameter: list size at or below which insertion sort will be + * used in preference to mergesort. + * To be removed in a future release. + */ + private static final int INSERTIONSORT_THRESHOLD = 7; + + /** + * Src is the source array that starts at index 0 + * Dest is the (possibly larger) array destination with a possible offset + * low is the index in dest to start sorting + * high is the end index in dest to end sorting + * off is the offset to generate corresponding low, high in src + * To be removed in a future release. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private static void mergeSort(Object[] src, + Object[] dest, + int low, + int high, + int off) { + int length = high - low; + + // Insertion sort on smallest arrays + if (length < INSERTIONSORT_THRESHOLD) { + for (int i=low; ilow && + ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--) + swap(dest, j, j-1); + return; + } + + // Recursively sort halves of dest into src + int destLow = low; + int destHigh = high; + low += off; + high += off; + int mid = (low + high) >>> 1; + mergeSort(dest, src, low, mid, -off); + mergeSort(dest, src, mid, high, -off); + + // If list is already sorted, just copy from src to dest. This is an + // optimization that results in faster sorts for nearly ordered lists. + if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) { + System.arraycopy(src, low, dest, destLow, length); + return; + } + + // Merge sorted halves (now in src) into dest + for(int i = destLow, p = low, q = mid; i < destHigh; i++) { + if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0) + dest[i] = src[p++]; + else + dest[i] = src[q++]; + } + } + + /** + * Swaps x[a] with x[b]. + */ + private static void swap(Object[] x, int a, int b) { + Object t = x[a]; + x[a] = x[b]; + x[b] = t; } /** @@ -1323,17 +1386,31 @@ public class Arrays { * {@code c.compare(e1, e2)} must not throw a {@code ClassCastException} * for any elements {@code e1} and {@code e2} in the array). * - *

This sort is not guaranteed to be stable: equal elements - * may be reordered as a result of the sort. + *

This sort is guaranteed to be stable: equal elements will + * not be reordered as a result of the sort. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(Object[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. + *

Implementation note: This implementation is a stable, adaptive, + * iterative mergesort that requires far fewer than n lg(n) comparisons + * when the input array is partially sorted, while offering the + * performance of a traditional mergesort when the input array is + * randomly ordered. If the input array is nearly sorted, the + * implementation requires approximately n comparisons. Temporary + * storage requirements vary from a small constant for nearly sorted + * input arrays to n/2 object references for randomly ordered input + * arrays. + * + *

The implementation takes equal advantage of ascending and + * descending order in its input array, and can take advantage of + * ascending and descending order in different parts of the the same + * input array. It is well-suited to merging two or more sorted arrays: + * simply concatenate the arrays and sort the resulting array. + * + *

The implementation was adapted from Tim Peters's list sort for Python + * ( + * TimSort). It uses techiques from Peter McIlroy's "Optimistic + * Sorting and Information Theoretic Complexity", in Proceedings of the + * Fourth Annual ACM-SIAM Symposium on Discrete Algorithms, pp 467-474, + * January 1993. * * @param a the array to be sorted * @param c the comparator to determine the order of the array. A @@ -1342,12 +1419,24 @@ public class Arrays { * @throws ClassCastException if the array contains elements that are * not mutually comparable using the specified comparator * @throws IllegalArgumentException (optional) if the comparator is - * found to violate the {@link java.util.Comparator} contract - * - * @since 1.8 + * found to violate the {@link Comparator} contract */ - public static void parallelSort(T[] a, Comparator c) { - parallelSort(a, 0, a.length, c); + public static void sort(T[] a, Comparator c) { + if (c == null) + c = NaturalOrder.INSTANCE; + if (LegacyMergeSort.userRequested) + legacyMergeSort(a, c); + else + TimSort.sort(a, 0, a.length, c, null, 0, 0); + } + + /** To be removed in a future release. */ + private static void legacyMergeSort(T[] a, Comparator c) { + T[] aux = a.clone(); + if (c==null) + mergeSort(aux, a, 0, a.length, 0); + else + mergeSort(aux, a, 0, a.length, 0, c); } /** @@ -1360,17 +1449,31 @@ public class Arrays { * {@code c.compare(e1, e2)} must not throw a {@code ClassCastException} * for any elements {@code e1} and {@code e2} in the range). * - *

This sort is not guaranteed to be stable: equal elements - * may be reordered as a result of the sort. + *

This sort is guaranteed to be stable: equal elements will + * not be reordered as a result of the sort. * - *

Implementation note: The sorting algorithm is a parallel sort-merge - * that breaks the array into sub-arrays that are themselves sorted and then - * merged. When the sub-array length reaches a minimum granularity, the - * sub-array is sorted using the appropriate {@link Arrays#sort(Object[]) - * Arrays.sort} method. The algorithm requires a working space equal to the - * size of the original array. The {@link - * java.util.concurrent.ForkJoinPool#commonPool() ForkJoin common pool} is - * used to execute any parallel tasks. + *

Implementation note: This implementation is a stable, adaptive, + * iterative mergesort that requires far fewer than n lg(n) comparisons + * when the input array is partially sorted, while offering the + * performance of a traditional mergesort when the input array is + * randomly ordered. If the input array is nearly sorted, the + * implementation requires approximately n comparisons. Temporary + * storage requirements vary from a small constant for nearly sorted + * input arrays to n/2 object references for randomly ordered input + * arrays. + * + *

The implementation takes equal advantage of ascending and + * descending order in its input array, and can take advantage of + * ascending and descending order in different parts of the the same + * input array. It is well-suited to merging two or more sorted arrays: + * simply concatenate the arrays and sort the resulting array. + * + *

The implementation was adapted from Tim Peters's list sort for Python + * ( + * TimSort). It uses techiques from Peter McIlroy's "Optimistic + * Sorting and Information Theoretic Complexity", in Proceedings of the + * Fourth Annual ACM-SIAM Symposium on Discrete Algorithms, pp 467-474, + * January 1993. * * @param a the array to be sorted * @param fromIndex the index of the first element (inclusive) to be @@ -1379,57 +1482,80 @@ public class Arrays { * @param c the comparator to determine the order of the array. A * {@code null} value indicates that the elements' * {@linkplain Comparable natural ordering} should be used. + * @throws ClassCastException if the array contains elements that are not + * mutually comparable using the specified comparator. * @throws IllegalArgumentException if {@code fromIndex > toIndex} or - * (optional) if the natural ordering of the array elements is - * found to violate the {@link Comparable} contract + * (optional) if the comparator is found to violate the + * {@link Comparator} contract * @throws ArrayIndexOutOfBoundsException if {@code fromIndex < 0} or * {@code toIndex > a.length} - * @throws ClassCastException if the array contains elements that are - * not mutually comparable (for example, strings and - * integers). - * - * @since 1.8 */ - public static void parallelSort(T[] a, int fromIndex, int toIndex, - Comparator c) { + public static void sort(T[] a, int fromIndex, int toIndex, + Comparator c) { + if (c == null) + c = NaturalOrder.INSTANCE; rangeCheck(a.length, fromIndex, toIndex); - int nelements = toIndex - fromIndex; - Class tc = a.getClass().getComponentType(); - @SuppressWarnings("unchecked") - T[] workspace = (T[])Array.newInstance(tc, a.length); - int gran = getSplitThreshold(nelements); - FJComparator.Sorter task = new FJComparator.Sorter<>(a, workspace, - fromIndex, - nelements, gran, c); - task.invoke(); + if (LegacyMergeSort.userRequested) + legacyMergeSort(a, fromIndex, toIndex, c); + else + TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0); } - /** - * Returns the size threshold for splitting into subtasks. - * By default, uses about 8 times as many tasks as threads - * - * @param n number of elements in the array to be processed - */ - private static int getSplitThreshold(int n) { - int p = java.util.concurrent.ForkJoinPool.getCommonPoolParallelism(); - int t = (p > 1) ? (1 + n / (p << 3)) : n; - return t < MIN_ARRAY_SORT_GRAN ? MIN_ARRAY_SORT_GRAN : t; + /** To be removed in a future release. */ + private static void legacyMergeSort(T[] a, int fromIndex, int toIndex, + Comparator c) { + T[] aux = copyOfRange(a, fromIndex, toIndex); + if (c==null) + mergeSort(aux, a, fromIndex, toIndex, -fromIndex); + else + mergeSort(aux, a, fromIndex, toIndex, -fromIndex, c); } /** - * Checks that {@code fromIndex} and {@code toIndex} are in - * the range and throws an appropriate exception, if they aren't. + * Src is the source array that starts at index 0 + * Dest is the (possibly larger) array destination with a possible offset + * low is the index in dest to start sorting + * high is the end index in dest to end sorting + * off is the offset into src corresponding to low in dest + * To be removed in a future release. */ - private static void rangeCheck(int length, int fromIndex, int toIndex) { - if (fromIndex > toIndex) { - throw new IllegalArgumentException( - "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); + @SuppressWarnings({"rawtypes", "unchecked"}) + private static void mergeSort(Object[] src, + Object[] dest, + int low, int high, int off, + Comparator c) { + int length = high - low; + + // Insertion sort on smallest arrays + if (length < INSERTIONSORT_THRESHOLD) { + for (int i=low; ilow && c.compare(dest[j-1], dest[j])>0; j--) + swap(dest, j, j-1); + return; } - if (fromIndex < 0) { - throw new ArrayIndexOutOfBoundsException(fromIndex); + + // Recursively sort halves of dest into src + int destLow = low; + int destHigh = high; + low += off; + high += off; + int mid = (low + high) >>> 1; + mergeSort(dest, src, low, mid, -off, c); + mergeSort(dest, src, mid, high, -off, c); + + // If list is already sorted, just copy from src to dest. This is an + // optimization that results in faster sorts for nearly ordered lists. + if (c.compare(src[mid-1], src[mid]) <= 0) { + System.arraycopy(src, low, dest, destLow, length); + return; } - if (toIndex > length) { - throw new ArrayIndexOutOfBoundsException(toIndex); + + // Merge sorted halves (now in src) into dest + for(int i = destLow, p = low, q = mid; i < destHigh; i++) { + if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0) + dest[i] = src[p++]; + else + dest[i] = src[q++]; } } diff --git a/src/share/classes/java/util/ArraysParallelSortHelpers.java b/src/share/classes/java/util/ArraysParallelSortHelpers.java index f759d81555022c569cc47df57a77c84b3d13d40a..bc08be67e607a85cadc1d5693d8f297814c1eef2 100644 --- a/src/share/classes/java/util/ArraysParallelSortHelpers.java +++ b/src/share/classes/java/util/ArraysParallelSortHelpers.java @@ -25,6 +25,7 @@ package java.util; import java.util.concurrent.RecursiveAction; +import java.util.concurrent.CountedCompleter; /** * Helper utilities for the parallel sort methods in Arrays.parallelSort. @@ -44,1180 +45,966 @@ import java.util.concurrent.RecursiveAction; * c. merge them together * 3. merge together the two halves. * - * One reason for splitting in quarters is that this guarantees - * that the final sort is in the main array, not the workspace - * array. (workspace and main swap roles on each subsort step.) - * Leaf-level sorts use a Sequential quicksort, that in turn uses - * insertion sort if under threshold. Otherwise it uses median of - * three to pick pivot, and loops rather than recurses along left - * path. + * One reason for splitting in quarters is that this guarantees that + * the final sort is in the main array, not the workspace array. + * (workspace and main swap roles on each subsort step.) Leaf-level + * sorts use the associated sequential sort. * + * Merger classes perform merging for Sorter. They are structured + * such that if the underlying sort is stable (as is true for + * TimSort), then so is the full sort. If big enough, they split the + * largest of the two partitions in half, find the greatest point in + * smaller partition less than the beginning of the second half of + * larger via binary search; and then merge in parallel the two + * partitions. In part to ensure tasks are triggered in + * stability-preserving order, the current CountedCompleter design + * requires some little tasks to serve as place holders for triggering + * completion tasks. These classes (EmptyCompleter and Relay) don't + * need to keep track of the arrays, and are never themselves forked, + * so don't hold any task state. * - * Merger classes perform merging for Sorter. If big enough, splits Left - * partition in half; finds the greatest point in Right partition - * less than the beginning of the second half of Left via binary - * search; and then, in parallel, merges left half of Left with - * elements of Right up to split point, and merges right half of - * Left with elements of R past split point. At leaf, it just - * sequentially merges. This is all messy to code; sadly we need - * distinct versions for each type. + * The primitive class versions (FJByte... FJDouble) are + * identical to each other except for type declarations. * + * The base sequential sorts rely on non-public versions of TimSort, + * ComparableTimSort, and DualPivotQuicksort sort methods that accept + * temp workspace array slices that we will have already allocated, so + * avoids redundant allocation. (Except for DualPivotQuicksort byte[] + * sort, that does not ever use a workspace array.) */ /*package*/ class ArraysParallelSortHelpers { - // RFE: we should only need a working array as large as the subarray - // to be sorted, but the logic assumes that indices in the two - // arrays always line-up + /* + * Style note: The task classes have a lot of parameters, that are + * stored as task fields and copied to local variables and used in + * compute() methods, We pack these into as few lines as possible, + * and hoist consistency checks among them before main loops, to + * reduce distraction. + */ + + /** + * A placeholder task for Sorters, used for the lowest + * quartile task, that does not need to maintain array state. + */ + static final class EmptyCompleter extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + EmptyCompleter(CountedCompleter p) { super(p); } + public final void compute() { } + } - /** byte support class */ - static final class FJByte { - static final class Sorter extends RecursiveAction { - static final long serialVersionUID = 749471161188027634L; - final byte[] a; // array to be sorted. - final byte[] w; // workspace for merge - final int origin; // origin of the part of array we deal with - final int n; // Number of elements in (sub)arrays. - final int gran; // split control + /** + * A trigger for secondary merge of two merges + */ + static final class Relay extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final CountedCompleter task; + Relay(CountedCompleter task) { + super(null, 1); + this.task = task; + } + public final void compute() { } + public final void onCompletion(CountedCompleter t) { + task.compute(); + } + } - Sorter(byte[] a, byte[] w, int origin, int n, int gran) { - this.a = a; - this.w = w; - this.origin = origin; - this.n = n; - this.gran = gran; + /** Object + Comparator support class */ + static final class FJObject { + static final class Sorter extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final T[] a, w; + final int base, size, wbase, gran; + Comparator comparator; + Sorter(CountedCompleter par, T[] a, T[] w, int base, int size, + int wbase, int gran, + Comparator comparator) { + super(par); + this.a = a; this.w = w; this.base = base; this.size = size; + this.wbase = wbase; this.gran = gran; + this.comparator = comparator; + } + public final void compute() { + CountedCompleter s = this; + Comparator c = this.comparator; + T[] a = this.a, w = this.w; // localize all params + int b = this.base, n = this.size, wb = this.wbase, g = this.gran; + while (n > g) { + int h = n >>> 1, q = h >>> 1, u = h + q; // quartiles + Relay fc = new Relay(new Merger(s, w, a, wb, h, + wb+h, n-h, b, g, c)); + Relay rc = new Relay(new Merger(fc, a, w, b+h, q, + b+u, n-u, wb+h, g, c)); + new Sorter(rc, a, w, b+u, n-u, wb+u, g, c).fork(); + new Sorter(rc, a, w, b+h, q, wb+h, g, c).fork();; + Relay bc = new Relay(new Merger(fc, a, w, b, q, + b+q, h-q, wb, g, c)); + new Sorter(bc, a, w, b+q, h-q, wb+q, g, c).fork(); + s = new EmptyCompleter(bc); + n = q; + } + TimSort.sort(a, b, b + n, c, w, wb, n); + s.tryComplete(); } + } - public void compute() { - final int l = origin; - final int g = gran; - final int n = this.n; - final byte[] a = this.a; - final byte[] w = this.w; - if (n > g) { - int h = n >>> 1; // half - int q = n >>> 2; // lower quarter index - int u = h + q; // upper quarter - FJSubSorter ls = new FJSubSorter(new Sorter(a, w, l, q, g), - new Sorter(a, w, l+q, h-q, g), - new Merger(a, w, l, q, - l+q, h-q, l, g, null)); - FJSubSorter rs = new FJSubSorter(new Sorter(a, w, l+h, q, g), - new Sorter(a, w, l+u, n-u, g), - new Merger(a, w, l+h, q, - l+u, n-u, l+h, g, null)); - rs.fork(); - ls.compute(); - if (rs.tryUnfork()) rs.compute(); else rs.join(); - new Merger(w, a, l, h, - l+h, n-h, l, g, null).compute(); - } else { - DualPivotQuicksort.sort(a, l, l+n-1); //skip rangeCheck + static final class Merger extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final T[] a, w; // main and workspace arrays + final int lbase, lsize, rbase, rsize, wbase, gran; + Comparator comparator; + Merger(CountedCompleter par, T[] a, T[] w, + int lbase, int lsize, int rbase, + int rsize, int wbase, int gran, + Comparator comparator) { + super(par); + this.a = a; this.w = w; + this.lbase = lbase; this.lsize = lsize; + this.rbase = rbase; this.rsize = rsize; + this.wbase = wbase; this.gran = gran; + this.comparator = comparator; + } + + public final void compute() { + Comparator c = this.comparator; + T[] a = this.a, w = this.w; // localize all params + int lb = this.lbase, ln = this.lsize, rb = this.rbase, + rn = this.rsize, k = this.wbase, g = this.gran; + if (a == null || w == null || lb < 0 || rb < 0 || k < 0 || + c == null) + throw new IllegalStateException(); // hoist checks + for (int lh, rh;;) { // split larger, find point in smaller + if (ln >= rn) { + if (ln <= g) + break; + rh = rn; + T split = a[(lh = ln >>> 1) + lb]; + for (int lo = 0; lo < rh; ) { + int rm = (lo + rh) >>> 1; + if (c.compare(split, a[rm + rb]) <= 0) + rh = rm; + else + lo = rm + 1; + } + } + else { + if (rn <= g) + break; + lh = ln; + T split = a[(rh = rn >>> 1) + rb]; + for (int lo = 0; lo < lh; ) { + int lm = (lo + lh) >>> 1; + if (c.compare(split, a[lm + lb]) <= 0) + lh = lm; + else + lo = lm + 1; + } + } + Merger m = new Merger(this, a, w, lb + lh, ln - lh, + rb + rh, rn - rh, + k + lh + rh, g, c); + rn = rh; + ln = lh; + addToPendingCount(1); + m.fork(); + } + + int lf = lb + ln, rf = rb + rn; // index bounds + while (lb < lf && rb < rf) { + T t, al, ar; + if (c.compare((al = a[lb]), (ar = a[rb])) <= 0) { + lb++; t = al; + } + else { + rb++; t = ar; + } + w[k++] = t; } + if (rb < rf) + System.arraycopy(a, rb, w, k, rf - rb); + else if (lb < lf) + System.arraycopy(a, lb, w, k, lf - lb); + + tryComplete(); } - } - static final class Merger extends RecursiveAction { - static final long serialVersionUID = -9090258248781844470L; - final byte[] a; - final byte[] w; - final int lo; - final int ln; - final int ro; - final int rn; - final int wo; - final int gran; - final Merger next; + } + } // FJObject - Merger(byte[] a, byte[] w, int lo, int ln, int ro, int rn, int wo, - int gran, Merger next) { - this.a = a; - this.w = w; - this.lo = lo; - this.ln = ln; - this.ro = ro; - this.rn = rn; - this.wo = wo; - this.gran = gran; - this.next = next; + /** byte support class */ + static final class FJByte { + static final class Sorter extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final byte[] a, w; + final int base, size, wbase, gran; + Sorter(CountedCompleter par, byte[] a, byte[] w, int base, + int size, int wbase, int gran) { + super(par); + this.a = a; this.w = w; this.base = base; this.size = size; + this.wbase = wbase; this.gran = gran; + } + public final void compute() { + CountedCompleter s = this; + byte[] a = this.a, w = this.w; // localize all params + int b = this.base, n = this.size, wb = this.wbase, g = this.gran; + while (n > g) { + int h = n >>> 1, q = h >>> 1, u = h + q; // quartiles + Relay fc = new Relay(new Merger(s, w, a, wb, h, + wb+h, n-h, b, g)); + Relay rc = new Relay(new Merger(fc, a, w, b+h, q, + b+u, n-u, wb+h, g)); + new Sorter(rc, a, w, b+u, n-u, wb+u, g).fork(); + new Sorter(rc, a, w, b+h, q, wb+h, g).fork();; + Relay bc = new Relay(new Merger(fc, a, w, b, q, + b+q, h-q, wb, g)); + new Sorter(bc, a, w, b+q, h-q, wb+q, g).fork(); + s = new EmptyCompleter(bc); + n = q; + } + DualPivotQuicksort.sort(a, b, b + n - 1); + s.tryComplete(); } + } - public void compute() { - final byte[] a = this.a; - final byte[] w = this.w; - Merger rights = null; - int nleft = ln; - int nright = rn; - while (nleft > gran) { - int lh = nleft >>> 1; - int splitIndex = lo + lh; - byte split = a[splitIndex]; - int rl = 0; - int rh = nright; - while (rl < rh) { - int mid = (rl + rh) >>> 1; - if (split <= a[ro + mid]) - rh = mid; - else - rl = mid + 1; + static final class Merger extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final byte[] a, w; // main and workspace arrays + final int lbase, lsize, rbase, rsize, wbase, gran; + Merger(CountedCompleter par, byte[] a, byte[] w, + int lbase, int lsize, int rbase, + int rsize, int wbase, int gran) { + super(par); + this.a = a; this.w = w; + this.lbase = lbase; this.lsize = lsize; + this.rbase = rbase; this.rsize = rsize; + this.wbase = wbase; this.gran = gran; + } + + public final void compute() { + byte[] a = this.a, w = this.w; // localize all params + int lb = this.lbase, ln = this.lsize, rb = this.rbase, + rn = this.rsize, k = this.wbase, g = this.gran; + if (a == null || w == null || lb < 0 || rb < 0 || k < 0) + throw new IllegalStateException(); // hoist checks + for (int lh, rh;;) { // split larger, find point in smaller + if (ln >= rn) { + if (ln <= g) + break; + rh = rn; + byte split = a[(lh = ln >>> 1) + lb]; + for (int lo = 0; lo < rh; ) { + int rm = (lo + rh) >>> 1; + if (split <= a[rm + rb]) + rh = rm; + else + lo = rm + 1; + } + } + else { + if (rn <= g) + break; + lh = ln; + byte split = a[(rh = rn >>> 1) + rb]; + for (int lo = 0; lo < lh; ) { + int lm = (lo + lh) >>> 1; + if (split <= a[lm + lb]) + lh = lm; + else + lo = lm + 1; + } + } + Merger m = new Merger(this, a, w, lb + lh, ln - lh, + rb + rh, rn - rh, + k + lh + rh, g); + rn = rh; + ln = lh; + addToPendingCount(1); + m.fork(); + } + + int lf = lb + ln, rf = rb + rn; // index bounds + while (lb < lf && rb < rf) { + byte t, al, ar; + if ((al = a[lb]) <= (ar = a[rb])) { + lb++; t = al; + } + else { + rb++; t = ar; } - (rights = new Merger(a, w, splitIndex, nleft-lh, ro+rh, - nright-rh, wo+lh+rh, gran, rights)).fork(); - nleft = lh; - nright = rh; - } - - int l = lo; - int lFence = l + nleft; - int r = ro; - int rFence = r + nright; - int k = wo; - while (l < lFence && r < rFence) { - byte al = a[l]; - byte ar = a[r]; - byte t; - if (al <= ar) {++l; t=al;} else {++r; t = ar;} w[k++] = t; } - while (l < lFence) - w[k++] = a[l++]; - while (r < rFence) - w[k++] = a[r++]; - while (rights != null) { - if (rights.tryUnfork()) - rights.compute(); - else - rights.join(); - rights = rights.next; - } + if (rb < rf) + System.arraycopy(a, rb, w, k, rf - rb); + else if (lb < lf) + System.arraycopy(a, lb, w, k, lf - lb); + tryComplete(); } } } // FJByte /** char support class */ static final class FJChar { - static final class Sorter extends RecursiveAction { - static final long serialVersionUID = 8723376019074596641L; - final char[] a; // array to be sorted. - final char[] w; // workspace for merge - final int origin; // origin of the part of array we deal with - final int n; // Number of elements in (sub)arrays. - final int gran; // split control - - Sorter(char[] a, char[] w, int origin, int n, int gran) { - this.a = a; - this.w = w; - this.origin = origin; - this.n = n; - this.gran = gran; - } - - public void compute() { - final int l = origin; - final int g = gran; - final int n = this.n; - final char[] a = this.a; - final char[] w = this.w; - if (n > g) { - int h = n >>> 1; // half - int q = n >>> 2; // lower quarter index - int u = h + q; // upper quarter - FJSubSorter ls = new FJSubSorter(new Sorter(a, w, l, q, g), - new Sorter(a, w, l+q, h-q, g), - new Merger(a, w, l, q, - l+q, h-q, l, g, null)); - FJSubSorter rs = new FJSubSorter(new Sorter(a, w, l + h, q, g), - new Sorter(a, w, l+u, n-u, g), - new Merger(a, w, l+h, q, - l+u, n-u, l+h, g, null)); - rs.fork(); - ls.compute(); - if (rs.tryUnfork()) rs.compute(); else rs.join(); - new Merger(w, a, l, h, l + h, n - h, l, g, null).compute(); - } else { - DualPivotQuicksort.sort(a, l, l+n-1); // skip rangeCheck - } + static final class Sorter extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final char[] a, w; + final int base, size, wbase, gran; + Sorter(CountedCompleter par, char[] a, char[] w, int base, + int size, int wbase, int gran) { + super(par); + this.a = a; this.w = w; this.base = base; this.size = size; + this.wbase = wbase; this.gran = gran; + } + public final void compute() { + CountedCompleter s = this; + char[] a = this.a, w = this.w; // localize all params + int b = this.base, n = this.size, wb = this.wbase, g = this.gran; + while (n > g) { + int h = n >>> 1, q = h >>> 1, u = h + q; // quartiles + Relay fc = new Relay(new Merger(s, w, a, wb, h, + wb+h, n-h, b, g)); + Relay rc = new Relay(new Merger(fc, a, w, b+h, q, + b+u, n-u, wb+h, g)); + new Sorter(rc, a, w, b+u, n-u, wb+u, g).fork(); + new Sorter(rc, a, w, b+h, q, wb+h, g).fork();; + Relay bc = new Relay(new Merger(fc, a, w, b, q, + b+q, h-q, wb, g)); + new Sorter(bc, a, w, b+q, h-q, wb+q, g).fork(); + s = new EmptyCompleter(bc); + n = q; + } + DualPivotQuicksort.sort(a, b, b + n - 1, w, wb, n); + s.tryComplete(); } } - static final class Merger extends RecursiveAction { - static final long serialVersionUID = -1383975444621698926L; - final char[] a; - final char[] w; - final int lo; - final int ln; - final int ro; - final int rn; - final int wo; - final int gran; - final Merger next; - - Merger(char[] a, char[] w, int lo, int ln, int ro, int rn, int wo, - int gran, Merger next) { - this.a = a; - this.w = w; - this.lo = lo; - this.ln = ln; - this.ro = ro; - this.rn = rn; - this.wo = wo; - this.gran = gran; - this.next = next; - } - - public void compute() { - final char[] a = this.a; - final char[] w = this.w; - Merger rights = null; - int nleft = ln; - int nright = rn; - while (nleft > gran) { - int lh = nleft >>> 1; - int splitIndex = lo + lh; - char split = a[splitIndex]; - int rl = 0; - int rh = nright; - while (rl < rh) { - int mid = (rl + rh) >>> 1; - if (split <= a[ro + mid]) - rh = mid; - else - rl = mid + 1; + static final class Merger extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final char[] a, w; // main and workspace arrays + final int lbase, lsize, rbase, rsize, wbase, gran; + Merger(CountedCompleter par, char[] a, char[] w, + int lbase, int lsize, int rbase, + int rsize, int wbase, int gran) { + super(par); + this.a = a; this.w = w; + this.lbase = lbase; this.lsize = lsize; + this.rbase = rbase; this.rsize = rsize; + this.wbase = wbase; this.gran = gran; + } + + public final void compute() { + char[] a = this.a, w = this.w; // localize all params + int lb = this.lbase, ln = this.lsize, rb = this.rbase, + rn = this.rsize, k = this.wbase, g = this.gran; + if (a == null || w == null || lb < 0 || rb < 0 || k < 0) + throw new IllegalStateException(); // hoist checks + for (int lh, rh;;) { // split larger, find point in smaller + if (ln >= rn) { + if (ln <= g) + break; + rh = rn; + char split = a[(lh = ln >>> 1) + lb]; + for (int lo = 0; lo < rh; ) { + int rm = (lo + rh) >>> 1; + if (split <= a[rm + rb]) + rh = rm; + else + lo = rm + 1; + } + } + else { + if (rn <= g) + break; + lh = ln; + char split = a[(rh = rn >>> 1) + rb]; + for (int lo = 0; lo < lh; ) { + int lm = (lo + lh) >>> 1; + if (split <= a[lm + lb]) + lh = lm; + else + lo = lm + 1; + } + } + Merger m = new Merger(this, a, w, lb + lh, ln - lh, + rb + rh, rn - rh, + k + lh + rh, g); + rn = rh; + ln = lh; + addToPendingCount(1); + m.fork(); + } + + int lf = lb + ln, rf = rb + rn; // index bounds + while (lb < lf && rb < rf) { + char t, al, ar; + if ((al = a[lb]) <= (ar = a[rb])) { + lb++; t = al; + } + else { + rb++; t = ar; } - (rights = new Merger(a, w, splitIndex, nleft-lh, ro+rh, - nright-rh, wo+lh+rh, gran, rights)).fork(); - nleft = lh; - nright = rh; - } - - int l = lo; - int lFence = l + nleft; - int r = ro; - int rFence = r + nright; - int k = wo; - while (l < lFence && r < rFence) { - char al = a[l]; - char ar = a[r]; - char t; - if (al <= ar) {++l; t=al;} else {++r; t = ar;} w[k++] = t; } - while (l < lFence) - w[k++] = a[l++]; - while (r < rFence) - w[k++] = a[r++]; - while (rights != null) { - if (rights.tryUnfork()) - rights.compute(); - else - rights.join(); - rights = rights.next; - } + if (rb < rf) + System.arraycopy(a, rb, w, k, rf - rb); + else if (lb < lf) + System.arraycopy(a, lb, w, k, lf - lb); + tryComplete(); } } } // FJChar /** short support class */ static final class FJShort { - static final class Sorter extends RecursiveAction { - static final long serialVersionUID = -7886754793730583084L; - final short[] a; // array to be sorted. - final short[] w; // workspace for merge - final int origin; // origin of the part of array we deal with - final int n; // Number of elements in (sub)arrays. - final int gran; // split control - - Sorter(short[] a, short[] w, int origin, int n, int gran) { - this.a = a; - this.w = w; - this.origin = origin; - this.n = n; - this.gran = gran; - } - - public void compute() { - final int l = origin; - final int g = gran; - final int n = this.n; - final short[] a = this.a; - final short[] w = this.w; - if (n > g) { - int h = n >>> 1; // half - int q = n >>> 2; // lower quarter index - int u = h + q; // upper quarter - FJSubSorter ls = new FJSubSorter(new Sorter(a, w, l, q, g), - new Sorter(a, w, l+q, h-q, g), - new Merger(a, w, l, q, - l+q, h-q, l, g, null)); - FJSubSorter rs = new FJSubSorter(new Sorter(a, w, l + h, q, g), - new Sorter(a, w, l+u, n-u, g), - new Merger(a, w, l+h, q, - l+u, n-u, l+h, g, null)); - rs.fork(); - ls.compute(); - if (rs.tryUnfork()) rs.compute(); else rs.join(); - new Merger(w, a, l, h, l + h, n - h, l, g, null).compute(); - } else { - DualPivotQuicksort.sort(a, l, l+n-1); // skip rangeCheck - } + static final class Sorter extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final short[] a, w; + final int base, size, wbase, gran; + Sorter(CountedCompleter par, short[] a, short[] w, int base, + int size, int wbase, int gran) { + super(par); + this.a = a; this.w = w; this.base = base; this.size = size; + this.wbase = wbase; this.gran = gran; + } + public final void compute() { + CountedCompleter s = this; + short[] a = this.a, w = this.w; // localize all params + int b = this.base, n = this.size, wb = this.wbase, g = this.gran; + while (n > g) { + int h = n >>> 1, q = h >>> 1, u = h + q; // quartiles + Relay fc = new Relay(new Merger(s, w, a, wb, h, + wb+h, n-h, b, g)); + Relay rc = new Relay(new Merger(fc, a, w, b+h, q, + b+u, n-u, wb+h, g)); + new Sorter(rc, a, w, b+u, n-u, wb+u, g).fork(); + new Sorter(rc, a, w, b+h, q, wb+h, g).fork();; + Relay bc = new Relay(new Merger(fc, a, w, b, q, + b+q, h-q, wb, g)); + new Sorter(bc, a, w, b+q, h-q, wb+q, g).fork(); + s = new EmptyCompleter(bc); + n = q; + } + DualPivotQuicksort.sort(a, b, b + n - 1, w, wb, n); + s.tryComplete(); } } - static final class Merger extends RecursiveAction { - static final long serialVersionUID = 3895749408536700048L; - final short[] a; - final short[] w; - final int lo; - final int ln; - final int ro; - final int rn; - final int wo; - final int gran; - final Merger next; - - Merger(short[] a, short[] w, int lo, int ln, int ro, int rn, int wo, - int gran, Merger next) { - this.a = a; - this.w = w; - this.lo = lo; - this.ln = ln; - this.ro = ro; - this.rn = rn; - this.wo = wo; - this.gran = gran; - this.next = next; - } - - public void compute() { - final short[] a = this.a; - final short[] w = this.w; - Merger rights = null; - int nleft = ln; - int nright = rn; - while (nleft > gran) { - int lh = nleft >>> 1; - int splitIndex = lo + lh; - short split = a[splitIndex]; - int rl = 0; - int rh = nright; - while (rl < rh) { - int mid = (rl + rh) >>> 1; - if (split <= a[ro + mid]) - rh = mid; - else - rl = mid + 1; + static final class Merger extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final short[] a, w; // main and workspace arrays + final int lbase, lsize, rbase, rsize, wbase, gran; + Merger(CountedCompleter par, short[] a, short[] w, + int lbase, int lsize, int rbase, + int rsize, int wbase, int gran) { + super(par); + this.a = a; this.w = w; + this.lbase = lbase; this.lsize = lsize; + this.rbase = rbase; this.rsize = rsize; + this.wbase = wbase; this.gran = gran; + } + + public final void compute() { + short[] a = this.a, w = this.w; // localize all params + int lb = this.lbase, ln = this.lsize, rb = this.rbase, + rn = this.rsize, k = this.wbase, g = this.gran; + if (a == null || w == null || lb < 0 || rb < 0 || k < 0) + throw new IllegalStateException(); // hoist checks + for (int lh, rh;;) { // split larger, find point in smaller + if (ln >= rn) { + if (ln <= g) + break; + rh = rn; + short split = a[(lh = ln >>> 1) + lb]; + for (int lo = 0; lo < rh; ) { + int rm = (lo + rh) >>> 1; + if (split <= a[rm + rb]) + rh = rm; + else + lo = rm + 1; + } + } + else { + if (rn <= g) + break; + lh = ln; + short split = a[(rh = rn >>> 1) + rb]; + for (int lo = 0; lo < lh; ) { + int lm = (lo + lh) >>> 1; + if (split <= a[lm + lb]) + lh = lm; + else + lo = lm + 1; + } + } + Merger m = new Merger(this, a, w, lb + lh, ln - lh, + rb + rh, rn - rh, + k + lh + rh, g); + rn = rh; + ln = lh; + addToPendingCount(1); + m.fork(); + } + + int lf = lb + ln, rf = rb + rn; // index bounds + while (lb < lf && rb < rf) { + short t, al, ar; + if ((al = a[lb]) <= (ar = a[rb])) { + lb++; t = al; + } + else { + rb++; t = ar; } - (rights = new Merger(a, w, splitIndex, nleft-lh, ro+rh, - nright-rh, wo+lh+rh, gran, rights)).fork(); - nleft = lh; - nright = rh; - } - - int l = lo; - int lFence = l + nleft; - int r = ro; - int rFence = r + nright; - int k = wo; - while (l < lFence && r < rFence) { - short al = a[l]; - short ar = a[r]; - short t; - if (al <= ar) {++l; t=al;} else {++r; t = ar;} w[k++] = t; } - while (l < lFence) - w[k++] = a[l++]; - while (r < rFence) - w[k++] = a[r++]; - while (rights != null) { - if (rights.tryUnfork()) - rights.compute(); - else - rights.join(); - rights = rights.next; - } + if (rb < rf) + System.arraycopy(a, rb, w, k, rf - rb); + else if (lb < lf) + System.arraycopy(a, lb, w, k, lf - lb); + tryComplete(); } } } // FJShort /** int support class */ static final class FJInt { - static final class Sorter extends RecursiveAction { - static final long serialVersionUID = 4263311808957292729L; - final int[] a; // array to be sorted. - final int[] w; // workspace for merge - final int origin; // origin of the part of array we deal with - final int n; // Number of elements in (sub)arrays. - final int gran; // split control - - Sorter(int[] a, int[] w, int origin, int n, int gran) { - this.a = a; - this.w = w; - this.origin = origin; - this.n = n; - this.gran = gran; - } - - public void compute() { - final int l = origin; - final int g = gran; - final int n = this.n; - final int[] a = this.a; - final int[] w = this.w; - if (n > g) { - int h = n >>> 1; // half - int q = n >>> 2; // lower quarter index - int u = h + q; // upper quarter - FJSubSorter ls = new FJSubSorter(new Sorter(a, w, l, q, g), - new Sorter(a, w, l+q, h-q, g), - new Merger(a, w, l, q, - l+q, h-q, l, g, null)); - FJSubSorter rs = new FJSubSorter(new Sorter(a, w, l + h, q, g), - new Sorter(a, w, l+u, n-u, g), - new Merger(a, w, l+h, q, - l+u, n-u, l+h, g, null)); - rs.fork(); - ls.compute(); - if (rs.tryUnfork()) rs.compute(); else rs.join(); - new Merger(w, a, l, h, l + h, n - h, l, g, null).compute(); - } else { - DualPivotQuicksort.sort(a, l, l+n-1); // skip rangeCheck - } + static final class Sorter extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final int[] a, w; + final int base, size, wbase, gran; + Sorter(CountedCompleter par, int[] a, int[] w, int base, + int size, int wbase, int gran) { + super(par); + this.a = a; this.w = w; this.base = base; this.size = size; + this.wbase = wbase; this.gran = gran; + } + public final void compute() { + CountedCompleter s = this; + int[] a = this.a, w = this.w; // localize all params + int b = this.base, n = this.size, wb = this.wbase, g = this.gran; + while (n > g) { + int h = n >>> 1, q = h >>> 1, u = h + q; // quartiles + Relay fc = new Relay(new Merger(s, w, a, wb, h, + wb+h, n-h, b, g)); + Relay rc = new Relay(new Merger(fc, a, w, b+h, q, + b+u, n-u, wb+h, g)); + new Sorter(rc, a, w, b+u, n-u, wb+u, g).fork(); + new Sorter(rc, a, w, b+h, q, wb+h, g).fork();; + Relay bc = new Relay(new Merger(fc, a, w, b, q, + b+q, h-q, wb, g)); + new Sorter(bc, a, w, b+q, h-q, wb+q, g).fork(); + s = new EmptyCompleter(bc); + n = q; + } + DualPivotQuicksort.sort(a, b, b + n - 1, w, wb, n); + s.tryComplete(); } } - static final class Merger extends RecursiveAction { - static final long serialVersionUID = -8727507284219982792L; - final int[] a; - final int[] w; - final int lo; - final int ln; - final int ro; - final int rn; - final int wo; - final int gran; - final Merger next; - - Merger(int[] a, int[] w, int lo, int ln, int ro, int rn, int wo, - int gran, Merger next) { - this.a = a; - this.w = w; - this.lo = lo; - this.ln = ln; - this.ro = ro; - this.rn = rn; - this.wo = wo; - this.gran = gran; - this.next = next; - } - - public void compute() { - final int[] a = this.a; - final int[] w = this.w; - Merger rights = null; - int nleft = ln; - int nright = rn; - while (nleft > gran) { - int lh = nleft >>> 1; - int splitIndex = lo + lh; - int split = a[splitIndex]; - int rl = 0; - int rh = nright; - while (rl < rh) { - int mid = (rl + rh) >>> 1; - if (split <= a[ro + mid]) - rh = mid; - else - rl = mid + 1; + static final class Merger extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final int[] a, w; // main and workspace arrays + final int lbase, lsize, rbase, rsize, wbase, gran; + Merger(CountedCompleter par, int[] a, int[] w, + int lbase, int lsize, int rbase, + int rsize, int wbase, int gran) { + super(par); + this.a = a; this.w = w; + this.lbase = lbase; this.lsize = lsize; + this.rbase = rbase; this.rsize = rsize; + this.wbase = wbase; this.gran = gran; + } + + public final void compute() { + int[] a = this.a, w = this.w; // localize all params + int lb = this.lbase, ln = this.lsize, rb = this.rbase, + rn = this.rsize, k = this.wbase, g = this.gran; + if (a == null || w == null || lb < 0 || rb < 0 || k < 0) + throw new IllegalStateException(); // hoist checks + for (int lh, rh;;) { // split larger, find point in smaller + if (ln >= rn) { + if (ln <= g) + break; + rh = rn; + int split = a[(lh = ln >>> 1) + lb]; + for (int lo = 0; lo < rh; ) { + int rm = (lo + rh) >>> 1; + if (split <= a[rm + rb]) + rh = rm; + else + lo = rm + 1; + } + } + else { + if (rn <= g) + break; + lh = ln; + int split = a[(rh = rn >>> 1) + rb]; + for (int lo = 0; lo < lh; ) { + int lm = (lo + lh) >>> 1; + if (split <= a[lm + lb]) + lh = lm; + else + lo = lm + 1; + } + } + Merger m = new Merger(this, a, w, lb + lh, ln - lh, + rb + rh, rn - rh, + k + lh + rh, g); + rn = rh; + ln = lh; + addToPendingCount(1); + m.fork(); + } + + int lf = lb + ln, rf = rb + rn; // index bounds + while (lb < lf && rb < rf) { + int t, al, ar; + if ((al = a[lb]) <= (ar = a[rb])) { + lb++; t = al; + } + else { + rb++; t = ar; } - (rights = new Merger(a, w, splitIndex, nleft-lh, ro+rh, - nright-rh, wo+lh+rh, gran, rights)).fork(); - nleft = lh; - nright = rh; - } - - int l = lo; - int lFence = l + nleft; - int r = ro; - int rFence = r + nright; - int k = wo; - while (l < lFence && r < rFence) { - int al = a[l]; - int ar = a[r]; - int t; - if (al <= ar) {++l; t=al;} else {++r; t = ar;} w[k++] = t; } - while (l < lFence) - w[k++] = a[l++]; - while (r < rFence) - w[k++] = a[r++]; - while (rights != null) { - if (rights.tryUnfork()) - rights.compute(); - else - rights.join(); - rights = rights.next; - } + if (rb < rf) + System.arraycopy(a, rb, w, k, rf - rb); + else if (lb < lf) + System.arraycopy(a, lb, w, k, lf - lb); + tryComplete(); } } } // FJInt /** long support class */ static final class FJLong { - static final class Sorter extends RecursiveAction { - static final long serialVersionUID = 6553695007444392455L; - final long[] a; // array to be sorted. - final long[] w; // workspace for merge - final int origin; // origin of the part of array we deal with - final int n; // Number of elements in (sub)arrays. - final int gran; // split control - - Sorter(long[] a, long[] w, int origin, int n, int gran) { - this.a = a; - this.w = w; - this.origin = origin; - this.n = n; - this.gran = gran; - } - - public void compute() { - final int l = origin; - final int g = gran; - final int n = this.n; - final long[] a = this.a; - final long[] w = this.w; - if (n > g) { - int h = n >>> 1; // half - int q = n >>> 2; // lower quarter index - int u = h + q; // upper quarter - FJSubSorter ls = new FJSubSorter(new Sorter(a, w, l, q, g), - new Sorter(a, w, l+q, h-q, g), - new Merger(a, w, l, q, - l+q, h-q, l, g, null)); - FJSubSorter rs = new FJSubSorter(new Sorter(a, w, l + h, q, g), - new Sorter(a, w, l+u, n-u, g), - new Merger(a, w, l+h, q, - l+u, n-u, l+h, g, null)); - rs.fork(); - ls.compute(); - if (rs.tryUnfork()) rs.compute(); else rs.join(); - new Merger(w, a, l, h, l + h, n - h, l, g, null).compute(); - } else { - DualPivotQuicksort.sort(a, l, l+n-1); // skip rangeCheck - } + static final class Sorter extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final long[] a, w; + final int base, size, wbase, gran; + Sorter(CountedCompleter par, long[] a, long[] w, int base, + int size, int wbase, int gran) { + super(par); + this.a = a; this.w = w; this.base = base; this.size = size; + this.wbase = wbase; this.gran = gran; + } + public final void compute() { + CountedCompleter s = this; + long[] a = this.a, w = this.w; // localize all params + int b = this.base, n = this.size, wb = this.wbase, g = this.gran; + while (n > g) { + int h = n >>> 1, q = h >>> 1, u = h + q; // quartiles + Relay fc = new Relay(new Merger(s, w, a, wb, h, + wb+h, n-h, b, g)); + Relay rc = new Relay(new Merger(fc, a, w, b+h, q, + b+u, n-u, wb+h, g)); + new Sorter(rc, a, w, b+u, n-u, wb+u, g).fork(); + new Sorter(rc, a, w, b+h, q, wb+h, g).fork();; + Relay bc = new Relay(new Merger(fc, a, w, b, q, + b+q, h-q, wb, g)); + new Sorter(bc, a, w, b+q, h-q, wb+q, g).fork(); + s = new EmptyCompleter(bc); + n = q; + } + DualPivotQuicksort.sort(a, b, b + n - 1, w, wb, n); + s.tryComplete(); } } - static final class Merger extends RecursiveAction { - static final long serialVersionUID = 8843567516333283861L; - final long[] a; - final long[] w; - final int lo; - final int ln; - final int ro; - final int rn; - final int wo; - final int gran; - final Merger next; - - Merger(long[] a, long[] w, int lo, int ln, int ro, int rn, int wo, - int gran, Merger next) { - this.a = a; - this.w = w; - this.lo = lo; - this.ln = ln; - this.ro = ro; - this.rn = rn; - this.wo = wo; - this.gran = gran; - this.next = next; - } - - public void compute() { - final long[] a = this.a; - final long[] w = this.w; - Merger rights = null; - int nleft = ln; - int nright = rn; - while (nleft > gran) { - int lh = nleft >>> 1; - int splitIndex = lo + lh; - long split = a[splitIndex]; - int rl = 0; - int rh = nright; - while (rl < rh) { - int mid = (rl + rh) >>> 1; - if (split <= a[ro + mid]) - rh = mid; - else - rl = mid + 1; + static final class Merger extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final long[] a, w; // main and workspace arrays + final int lbase, lsize, rbase, rsize, wbase, gran; + Merger(CountedCompleter par, long[] a, long[] w, + int lbase, int lsize, int rbase, + int rsize, int wbase, int gran) { + super(par); + this.a = a; this.w = w; + this.lbase = lbase; this.lsize = lsize; + this.rbase = rbase; this.rsize = rsize; + this.wbase = wbase; this.gran = gran; + } + + public final void compute() { + long[] a = this.a, w = this.w; // localize all params + int lb = this.lbase, ln = this.lsize, rb = this.rbase, + rn = this.rsize, k = this.wbase, g = this.gran; + if (a == null || w == null || lb < 0 || rb < 0 || k < 0) + throw new IllegalStateException(); // hoist checks + for (int lh, rh;;) { // split larger, find point in smaller + if (ln >= rn) { + if (ln <= g) + break; + rh = rn; + long split = a[(lh = ln >>> 1) + lb]; + for (int lo = 0; lo < rh; ) { + int rm = (lo + rh) >>> 1; + if (split <= a[rm + rb]) + rh = rm; + else + lo = rm + 1; + } + } + else { + if (rn <= g) + break; + lh = ln; + long split = a[(rh = rn >>> 1) + rb]; + for (int lo = 0; lo < lh; ) { + int lm = (lo + lh) >>> 1; + if (split <= a[lm + lb]) + lh = lm; + else + lo = lm + 1; + } + } + Merger m = new Merger(this, a, w, lb + lh, ln - lh, + rb + rh, rn - rh, + k + lh + rh, g); + rn = rh; + ln = lh; + addToPendingCount(1); + m.fork(); + } + + int lf = lb + ln, rf = rb + rn; // index bounds + while (lb < lf && rb < rf) { + long t, al, ar; + if ((al = a[lb]) <= (ar = a[rb])) { + lb++; t = al; + } + else { + rb++; t = ar; } - (rights = new Merger(a, w, splitIndex, nleft-lh, ro+rh, - nright-rh, wo+lh+rh, gran, rights)).fork(); - nleft = lh; - nright = rh; - } - - int l = lo; - int lFence = l + nleft; - int r = ro; - int rFence = r + nright; - int k = wo; - while (l < lFence && r < rFence) { - long al = a[l]; - long ar = a[r]; - long t; - if (al <= ar) {++l; t=al;} else {++r; t = ar;} w[k++] = t; } - while (l < lFence) - w[k++] = a[l++]; - while (r < rFence) - w[k++] = a[r++]; - while (rights != null) { - if (rights.tryUnfork()) - rights.compute(); - else - rights.join(); - rights = rights.next; - } + if (rb < rf) + System.arraycopy(a, rb, w, k, rf - rb); + else if (lb < lf) + System.arraycopy(a, lb, w, k, lf - lb); + tryComplete(); } } } // FJLong /** float support class */ static final class FJFloat { - static final class Sorter extends RecursiveAction { - static final long serialVersionUID = 1602600178202763377L; - final float[] a; // array to be sorted. - final float[] w; // workspace for merge - final int origin; // origin of the part of array we deal with - final int n; // Number of elements in (sub)arrays. - final int gran; // split control - - Sorter(float[] a, float[] w, int origin, int n, int gran) { - this.a = a; - this.w = w; - this.origin = origin; - this.n = n; - this.gran = gran; - } - - public void compute() { - final int l = origin; - final int g = gran; - final int n = this.n; - final float[] a = this.a; - final float[] w = this.w; - if (n > g) { - int h = n >>> 1; // half - int q = n >>> 2; // lower quarter index - int u = h + q; // upper quarter - FJSubSorter ls = new FJSubSorter(new Sorter(a, w, l, q, g), - new Sorter(a, w, l+q, h-q, g), - new Merger(a, w, l, q, - l+q, h-q, l, g, null)); - FJSubSorter rs = new FJSubSorter(new Sorter(a, w, l + h, q, g), - new Sorter(a, w, l+u, n-u, g), - new Merger(a, w, l+h, q, - l+u, n-u, l+h, g, null)); - rs.fork(); - ls.compute(); - if (rs.tryUnfork()) rs.compute(); else rs.join(); - new Merger(w, a, l, h, l + h, n - h, l, g, null).compute(); - } else { - DualPivotQuicksort.sort(a, l, l+n-1); // skip rangeCheck - } + static final class Sorter extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final float[] a, w; + final int base, size, wbase, gran; + Sorter(CountedCompleter par, float[] a, float[] w, int base, + int size, int wbase, int gran) { + super(par); + this.a = a; this.w = w; this.base = base; this.size = size; + this.wbase = wbase; this.gran = gran; + } + public final void compute() { + CountedCompleter s = this; + float[] a = this.a, w = this.w; // localize all params + int b = this.base, n = this.size, wb = this.wbase, g = this.gran; + while (n > g) { + int h = n >>> 1, q = h >>> 1, u = h + q; // quartiles + Relay fc = new Relay(new Merger(s, w, a, wb, h, + wb+h, n-h, b, g)); + Relay rc = new Relay(new Merger(fc, a, w, b+h, q, + b+u, n-u, wb+h, g)); + new Sorter(rc, a, w, b+u, n-u, wb+u, g).fork(); + new Sorter(rc, a, w, b+h, q, wb+h, g).fork();; + Relay bc = new Relay(new Merger(fc, a, w, b, q, + b+q, h-q, wb, g)); + new Sorter(bc, a, w, b+q, h-q, wb+q, g).fork(); + s = new EmptyCompleter(bc); + n = q; + } + DualPivotQuicksort.sort(a, b, b + n - 1, w, wb, n); + s.tryComplete(); } } - static final class Merger extends RecursiveAction { - static final long serialVersionUID = 1518176433845397426L; - final float[] a; - final float[] w; - final int lo; - final int ln; - final int ro; - final int rn; - final int wo; - final int gran; - final Merger next; - - Merger(float[] a, float[] w, int lo, int ln, int ro, int rn, int wo, - int gran, Merger next) { - this.a = a; - this.w = w; - this.lo = lo; - this.ln = ln; - this.ro = ro; - this.rn = rn; - this.wo = wo; - this.gran = gran; - this.next = next; - } - - public void compute() { - final float[] a = this.a; - final float[] w = this.w; - Merger rights = null; - int nleft = ln; - int nright = rn; - while (nleft > gran) { - int lh = nleft >>> 1; - int splitIndex = lo + lh; - float split = a[splitIndex]; - int rl = 0; - int rh = nright; - while (rl < rh) { - int mid = (rl + rh) >>> 1; - if (Float.compare(split, a[ro+mid]) <= 0) - rh = mid; - else - rl = mid + 1; + static final class Merger extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final float[] a, w; // main and workspace arrays + final int lbase, lsize, rbase, rsize, wbase, gran; + Merger(CountedCompleter par, float[] a, float[] w, + int lbase, int lsize, int rbase, + int rsize, int wbase, int gran) { + super(par); + this.a = a; this.w = w; + this.lbase = lbase; this.lsize = lsize; + this.rbase = rbase; this.rsize = rsize; + this.wbase = wbase; this.gran = gran; + } + + public final void compute() { + float[] a = this.a, w = this.w; // localize all params + int lb = this.lbase, ln = this.lsize, rb = this.rbase, + rn = this.rsize, k = this.wbase, g = this.gran; + if (a == null || w == null || lb < 0 || rb < 0 || k < 0) + throw new IllegalStateException(); // hoist checks + for (int lh, rh;;) { // split larger, find point in smaller + if (ln >= rn) { + if (ln <= g) + break; + rh = rn; + float split = a[(lh = ln >>> 1) + lb]; + for (int lo = 0; lo < rh; ) { + int rm = (lo + rh) >>> 1; + if (split <= a[rm + rb]) + rh = rm; + else + lo = rm + 1; + } } - (rights = new Merger(a, w, splitIndex, nleft-lh, ro+rh, - nright-rh, wo+lh+rh, gran, rights)).fork(); - nleft = lh; - nright = rh; - } - - int l = lo; - int lFence = l + nleft; - int r = ro; - int rFence = r + nright; - int k = wo; - while (l < lFence && r < rFence) { - float al = a[l]; - float ar = a[r]; - float t; - if (Float.compare(al, ar) <= 0) { - ++l; - t = al; - } else { - ++r; - t = ar; + else { + if (rn <= g) + break; + lh = ln; + float split = a[(rh = rn >>> 1) + rb]; + for (int lo = 0; lo < lh; ) { + int lm = (lo + lh) >>> 1; + if (split <= a[lm + lb]) + lh = lm; + else + lo = lm + 1; + } + } + Merger m = new Merger(this, a, w, lb + lh, ln - lh, + rb + rh, rn - rh, + k + lh + rh, g); + rn = rh; + ln = lh; + addToPendingCount(1); + m.fork(); + } + + int lf = lb + ln, rf = rb + rn; // index bounds + while (lb < lf && rb < rf) { + float t, al, ar; + if ((al = a[lb]) <= (ar = a[rb])) { + lb++; t = al; + } + else { + rb++; t = ar; } w[k++] = t; } - while (l < lFence) - w[k++] = a[l++]; - while (r < rFence) - w[k++] = a[r++]; - while (rights != null) { - if (rights.tryUnfork()) - rights.compute(); - else - rights.join(); - rights = rights.next; - } + if (rb < rf) + System.arraycopy(a, rb, w, k, rf - rb); + else if (lb < lf) + System.arraycopy(a, lb, w, k, lf - lb); + tryComplete(); } } } // FJFloat /** double support class */ static final class FJDouble { - static final class Sorter extends RecursiveAction { + static final class Sorter extends CountedCompleter { static final long serialVersionUID = 2446542900576103244L; - final double[] a; // array to be sorted. - final double[] w; // workspace for merge - final int origin; // origin of the part of array we deal with - final int n; // Number of elements in (sub)arrays. - final int gran; // split control - - Sorter(double[] a, double[] w, int origin, int n, int gran) { - this.a = a; - this.w = w; - this.origin = origin; - this.n = n; - this.gran = gran; - } - - public void compute() { - final int l = origin; - final int g = gran; - final int n = this.n; - final double[] a = this.a; - final double[] w = this.w; - if (n > g) { - int h = n >>> 1; // half - int q = n >>> 2; // lower quarter index - int u = h + q; // upper quarter - FJSubSorter ls = new FJSubSorter(new Sorter(a, w, l, q, g), - new Sorter(a, w, l+q, h-q, g), - new Merger(a, w, l, q, - l+q, h-q, l, g, null)); - FJSubSorter rs = new FJSubSorter(new Sorter(a, w, l + h, q, g), - new Sorter(a, w, l+u, n-u, g), - new Merger(a, w, l+h, q, - l+u, n-u, l+h, g, null)); - rs.fork(); - ls.compute(); - if (rs.tryUnfork()) rs.compute(); else rs.join(); - new Merger(w, a, l, h, l + h, n - h, l, g, null).compute(); - } else { - DualPivotQuicksort.sort(a, l, l+n-1); // skip rangeCheck - } + final double[] a, w; + final int base, size, wbase, gran; + Sorter(CountedCompleter par, double[] a, double[] w, int base, + int size, int wbase, int gran) { + super(par); + this.a = a; this.w = w; this.base = base; this.size = size; + this.wbase = wbase; this.gran = gran; + } + public final void compute() { + CountedCompleter s = this; + double[] a = this.a, w = this.w; // localize all params + int b = this.base, n = this.size, wb = this.wbase, g = this.gran; + while (n > g) { + int h = n >>> 1, q = h >>> 1, u = h + q; // quartiles + Relay fc = new Relay(new Merger(s, w, a, wb, h, + wb+h, n-h, b, g)); + Relay rc = new Relay(new Merger(fc, a, w, b+h, q, + b+u, n-u, wb+h, g)); + new Sorter(rc, a, w, b+u, n-u, wb+u, g).fork(); + new Sorter(rc, a, w, b+h, q, wb+h, g).fork();; + Relay bc = new Relay(new Merger(fc, a, w, b, q, + b+q, h-q, wb, g)); + new Sorter(bc, a, w, b+q, h-q, wb+q, g).fork(); + s = new EmptyCompleter(bc); + n = q; + } + DualPivotQuicksort.sort(a, b, b + n - 1, w, wb, n); + s.tryComplete(); } } - static final class Merger extends RecursiveAction { - static final long serialVersionUID = 8076242187166127592L; - final double[] a; - final double[] w; - final int lo; - final int ln; - final int ro; - final int rn; - final int wo; - final int gran; - final Merger next; - - Merger(double[] a, double[] w, int lo, int ln, int ro, int rn, int wo, - int gran, Merger next) { - this.a = a; - this.w = w; - this.lo = lo; - this.ln = ln; - this.ro = ro; - this.rn = rn; - this.wo = wo; - this.gran = gran; - this.next = next; - } - - public void compute() { - final double[] a = this.a; - final double[] w = this.w; - Merger rights = null; - int nleft = ln; - int nright = rn; - while (nleft > gran) { - int lh = nleft >>> 1; - int splitIndex = lo + lh; - double split = a[splitIndex]; - int rl = 0; - int rh = nright; - while (rl < rh) { - int mid = (rl + rh) >>> 1; - if (Double.compare(split, a[ro+mid]) <= 0) - rh = mid; - else - rl = mid + 1; + static final class Merger extends CountedCompleter { + static final long serialVersionUID = 2446542900576103244L; + final double[] a, w; // main and workspace arrays + final int lbase, lsize, rbase, rsize, wbase, gran; + Merger(CountedCompleter par, double[] a, double[] w, + int lbase, int lsize, int rbase, + int rsize, int wbase, int gran) { + super(par); + this.a = a; this.w = w; + this.lbase = lbase; this.lsize = lsize; + this.rbase = rbase; this.rsize = rsize; + this.wbase = wbase; this.gran = gran; + } + + public final void compute() { + double[] a = this.a, w = this.w; // localize all params + int lb = this.lbase, ln = this.lsize, rb = this.rbase, + rn = this.rsize, k = this.wbase, g = this.gran; + if (a == null || w == null || lb < 0 || rb < 0 || k < 0) + throw new IllegalStateException(); // hoist checks + for (int lh, rh;;) { // split larger, find point in smaller + if (ln >= rn) { + if (ln <= g) + break; + rh = rn; + double split = a[(lh = ln >>> 1) + lb]; + for (int lo = 0; lo < rh; ) { + int rm = (lo + rh) >>> 1; + if (split <= a[rm + rb]) + rh = rm; + else + lo = rm + 1; + } } - (rights = new Merger(a, w, splitIndex, nleft-lh, ro+rh, - nright-rh, wo+lh+rh, gran, rights)).fork(); - nleft = lh; - nright = rh; - } - - int l = lo; - int lFence = l + nleft; - int r = ro; - int rFence = r + nright; - int k = wo; - while (l < lFence && r < rFence) { - double al = a[l]; - double ar = a[r]; - double t; - if (Double.compare(al, ar) <= 0) { - ++l; - t = al; - } else { - ++r; - t = ar; + else { + if (rn <= g) + break; + lh = ln; + double split = a[(rh = rn >>> 1) + rb]; + for (int lo = 0; lo < lh; ) { + int lm = (lo + lh) >>> 1; + if (split <= a[lm + lb]) + lh = lm; + else + lo = lm + 1; + } } - w[k++] = t; - } - while (l < lFence) - w[k++] = a[l++]; - while (r < rFence) - w[k++] = a[r++]; - while (rights != null) { - if (rights.tryUnfork()) - rights.compute(); - else - rights.join(); - rights = rights.next; - } - } - } - } // FJDouble - - /** Comparable support class */ - static final class FJComparable { - static final class Sorter> extends RecursiveAction { - static final long serialVersionUID = -1024003289463302522L; - final T[] a; - final T[] w; - final int origin; - final int n; - final int gran; - - Sorter(T[] a, T[] w, int origin, int n, int gran) { - this.a = a; - this.w = w; - this.origin = origin; - this.n = n; - this.gran = gran; - } - - public void compute() { - final int l = origin; - final int g = gran; - final int n = this.n; - final T[] a = this.a; - final T[] w = this.w; - if (n > g) { - int h = n >>> 1; - int q = n >>> 2; - int u = h + q; - FJSubSorter ls = new FJSubSorter(new Sorter<>(a, w, l, q, g), - new Sorter<>(a, w, l+q, h-q, g), - new Merger<>(a, w, l, q, - l+q, h-q, l, g, null)); - FJSubSorter rs = new FJSubSorter(new Sorter<>(a, w, l+h, q, g), - new Sorter<>(a, w, l+u, n-u, g), - new Merger<>(a, w, l+h, q, - l+u, n-u, l+h, g, null)); - rs.fork(); - ls.compute(); - if (rs.tryUnfork()) rs.compute(); else rs.join(); - new Merger<>(w, a, l, h, l + h, n - h, l, g, null).compute(); - } else { - Arrays.sort(a, l, l+n); - } - } - } - - static final class Merger> extends RecursiveAction { - static final long serialVersionUID = -3989771675258379302L; - final T[] a; - final T[] w; - final int lo; - final int ln; - final int ro; - final int rn; - final int wo; - final int gran; - final Merger next; - - Merger(T[] a, T[] w, int lo, int ln, int ro, int rn, int wo, - int gran, Merger next) { - this.a = a; - this.w = w; - this.lo = lo; - this.ln = ln; - this.ro = ro; - this.rn = rn; - this.wo = wo; - this.gran = gran; - this.next = next; - } - - public void compute() { - final T[] a = this.a; - final T[] w = this.w; - Merger rights = null; - int nleft = ln; - int nright = rn; - while (nleft > gran) { - int lh = nleft >>> 1; - int splitIndex = lo + lh; - T split = a[splitIndex]; - int rl = 0; - int rh = nright; - while (rl < rh) { - int mid = (rl + rh) >>> 1; - if (split.compareTo(a[ro + mid]) <= 0) - rh = mid; - else - rl = mid + 1; + Merger m = new Merger(this, a, w, lb + lh, ln - lh, + rb + rh, rn - rh, + k + lh + rh, g); + rn = rh; + ln = lh; + addToPendingCount(1); + m.fork(); + } + + int lf = lb + ln, rf = rb + rn; // index bounds + while (lb < lf && rb < rf) { + double t, al, ar; + if ((al = a[lb]) <= (ar = a[rb])) { + lb++; t = al; } - (rights = new Merger<>(a, w, splitIndex, nleft-lh, ro+rh, - nright-rh, wo+lh+rh, gran, rights)).fork(); - nleft = lh; - nright = rh; - } - - int l = lo; - int lFence = l + nleft; - int r = ro; - int rFence = r + nright; - int k = wo; - while (l < lFence && r < rFence) { - T al = a[l]; - T ar = a[r]; - T t; - if (al.compareTo(ar) <= 0) {++l; t=al;} else {++r; t=ar; } - w[k++] = t; - } - while (l < lFence) - w[k++] = a[l++]; - while (r < rFence) - w[k++] = a[r++]; - while (rights != null) { - if (rights.tryUnfork()) - rights.compute(); - else - rights.join(); - rights = rights.next; - } - } - } - } // FJComparable - - /** Object + Comparator support class */ - static final class FJComparator { - static final class Sorter extends RecursiveAction { - static final long serialVersionUID = 9191600840025808581L; - final T[] a; // array to be sorted. - final T[] w; // workspace for merge - final int origin; // origin of the part of array we deal with - final int n; // Number of elements in (sub)arrays. - final int gran; // split control - final Comparator cmp; // Comparator to use - - Sorter(T[] a, T[] w, int origin, int n, int gran, Comparator cmp) { - this.a = a; - this.w = w; - this.origin = origin; - this.n = n; - this.cmp = cmp; - this.gran = gran; - } - - public void compute() { - final int l = origin; - final int g = gran; - final int n = this.n; - final T[] a = this.a; - final T[] w = this.w; - if (n > g) { - int h = n >>> 1; // half - int q = n >>> 2; // lower quarter index - int u = h + q; // upper quarter - FJSubSorter ls = new FJSubSorter(new Sorter<>(a, w, l, q, g, cmp), - new Sorter<>(a, w, l+q, h-q, g, cmp), - new Merger<>(a, w, l, q, - l+q, h-q, l, g, null, cmp)); - FJSubSorter rs = new FJSubSorter(new Sorter<>(a, w, l + h, q, g, cmp), - new Sorter<>(a, w, l+u, n-u, g, cmp), - new Merger<>(a, w, l+h, q, - l+u, n-u, l+h, g, null, cmp)); - rs.fork(); - ls.compute(); - if (rs.tryUnfork()) rs.compute(); else rs.join(); - new Merger<>(w, a, l, h, l + h, n - h, l, g, null, cmp).compute(); - } else { - Arrays.sort(a, l, l+n, cmp); - } - } - } - - static final class Merger extends RecursiveAction { - static final long serialVersionUID = -2679539040379156203L; - final T[] a; - final T[] w; - final int lo; - final int ln; - final int ro; - final int rn; - final int wo; - final int gran; - final Merger next; - final Comparator cmp; - - Merger(T[] a, T[] w, int lo, int ln, int ro, int rn, int wo, - int gran, Merger next, Comparator cmp) { - this.a = a; - this.w = w; - this.lo = lo; - this.ln = ln; - this.ro = ro; - this.rn = rn; - this.wo = wo; - this.gran = gran; - this.next = next; - this.cmp = cmp; - } - - public void compute() { - final T[] a = this.a; - final T[] w = this.w; - Merger rights = null; - int nleft = ln; - int nright = rn; - while (nleft > gran) { - int lh = nleft >>> 1; - int splitIndex = lo + lh; - T split = a[splitIndex]; - int rl = 0; - int rh = nright; - while (rl < rh) { - int mid = (rl + rh) >>> 1; - if (cmp.compare(split, a[ro+mid]) <= 0) - rh = mid; - else - rl = mid + 1; - } - (rights = new Merger<>(a, w, splitIndex, nleft-lh, ro+rh, - nright-rh, wo+lh+rh, gran, rights, cmp)).fork(); - nleft = lh; - nright = rh; - } - - int l = lo; - int lFence = l + nleft; - int r = ro; - int rFence = r + nright; - int k = wo; - while (l < lFence && r < rFence) { - T al = a[l]; - T ar = a[r]; - T t; - if (cmp.compare(al, ar) <= 0) { - ++l; - t = al; - } else { - ++r; - t = ar; + else { + rb++; t = ar; } w[k++] = t; } - while (l < lFence) - w[k++] = a[l++]; - while (r < rFence) - w[k++] = a[r++]; - while (rights != null) { - if (rights.tryUnfork()) - rights.compute(); - else - rights.join(); - rights = rights.next; - } + if (rb < rf) + System.arraycopy(a, rb, w, k, rf - rb); + else if (lb < lf) + System.arraycopy(a, lb, w, k, lf - lb); + tryComplete(); } } - } // FJComparator - - /** Utility class to sort half a partitioned array */ - private static final class FJSubSorter extends RecursiveAction { - static final long serialVersionUID = 9159249695527935512L; - final RecursiveAction left; - final RecursiveAction right; - final RecursiveAction merger; - - FJSubSorter(RecursiveAction left, RecursiveAction right, - RecursiveAction merger) { - this.left = left; - this.right = right; - this.merger = merger; - } + } // FJDouble - public void compute() { - right.fork(); - left.invoke(); - right.join(); - merger.invoke(); - } - } } diff --git a/src/share/classes/java/util/ComparableTimSort.java b/src/share/classes/java/util/ComparableTimSort.java index dd7cb3a80ecb4a91fc8863635f58512941b0ba16..76b5fd56a00f6c46963162291f39886a14b6ae15 100644 --- a/src/share/classes/java/util/ComparableTimSort.java +++ b/src/share/classes/java/util/ComparableTimSort.java @@ -86,9 +86,13 @@ class ComparableTimSort { private static final int INITIAL_TMP_STORAGE_LENGTH = 256; /** - * Temp storage for merges. + * Temp storage for merges. A workspace array may optionally be + * provided in constructor, and if so will be used as long as it + * is big enough. */ private Object[] tmp; + private int tmpBase; // base of tmp array slice + private int tmpLen; // length of tmp array slice /** * A stack of pending runs yet to be merged. Run i starts at @@ -108,15 +112,27 @@ class ComparableTimSort { * Creates a TimSort instance to maintain the state of an ongoing sort. * * @param a the array to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - private ComparableTimSort(Object[] a) { + private ComparableTimSort(Object[] a, Object[] work, int workBase, int workLen) { this.a = a; // Allocate temp storage (which may be increased later if necessary) int len = a.length; - Object[] newArray = new Object[len < 2 * INITIAL_TMP_STORAGE_LENGTH ? - len >>> 1 : INITIAL_TMP_STORAGE_LENGTH]; - tmp = newArray; + int tlen = (len < 2 * INITIAL_TMP_STORAGE_LENGTH) ? + len >>> 1 : INITIAL_TMP_STORAGE_LENGTH; + if (work == null || workLen < tlen || workBase + tlen > work.length) { + tmp = new Object[tlen]; + tmpBase = 0; + tmpLen = tlen; + } + else { + tmp = work; + tmpBase = workBase; + tmpLen = workLen; + } /* * Allocate runs-to-be-merged stack (which cannot be expanded). The @@ -136,17 +152,28 @@ class ComparableTimSort { } /* - * The next two methods (which are package private and static) constitute - * the entire API of this class. Each of these methods obeys the contract - * of the public method with the same signature in java.util.Arrays. + * The next method (package private and static) constitutes the + * entire API of this class. */ - static void sort(Object[] a) { - sort(a, 0, a.length); - } + /** + * Sorts the given range, using the given workspace array slice + * for temp storage when possible. This method is designed to be + * invoked from public methods (in class Arrays) after performing + * any necessary array bounds checks and expanding parameters into + * the required forms. + * + * @param a the array to be sorted + * @param lo the index of the first element, inclusive, to be sorted + * @param hi the index of the last element, exclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array + * @since 1.8 + */ + static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) { + assert a != null && lo >= 0 && lo <= hi && hi <= a.length; - static void sort(Object[] a, int lo, int hi) { - rangeCheck(a.length, lo, hi); int nRemaining = hi - lo; if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted @@ -163,7 +190,7 @@ class ComparableTimSort { * extending short natural runs to minRun elements, and merging runs * to maintain stack invariant. */ - ComparableTimSort ts = new ComparableTimSort(a); + ComparableTimSort ts = new ComparableTimSort(a, work, workBase, workLen); int minRun = minRunLength(nRemaining); do { // Identify next run @@ -619,11 +646,11 @@ class ComparableTimSort { // Copy first run into temp array Object[] a = this.a; // For performance Object[] tmp = ensureCapacity(len1); - System.arraycopy(a, base1, tmp, 0, len1); - int cursor1 = 0; // Indexes into tmp array + int cursor1 = tmpBase; // Indexes into tmp array int cursor2 = base2; // Indexes int a int dest = base1; // Indexes int a + System.arraycopy(a, base1, tmp, cursor1, len1); // Move first element of second run and deal with degenerate cases a[dest++] = a[cursor2++]; @@ -736,16 +763,17 @@ class ComparableTimSort { // Copy second run into temp array Object[] a = this.a; // For performance Object[] tmp = ensureCapacity(len2); - System.arraycopy(a, base2, tmp, 0, len2); + int tmpBase = this.tmpBase; + System.arraycopy(a, base2, tmp, tmpBase, len2); int cursor1 = base1 + len1 - 1; // Indexes into a - int cursor2 = len2 - 1; // Indexes into tmp array + int cursor2 = tmpBase + len2 - 1; // Indexes into tmp array int dest = base2 + len2 - 1; // Indexes into a // Move last element of first run and deal with degenerate cases a[dest--] = a[cursor1--]; if (--len1 == 0) { - System.arraycopy(tmp, 0, a, dest - (len2 - 1), len2); + System.arraycopy(tmp, tmpBase, a, dest - (len2 - 1), len2); return; } if (len2 == 1) { @@ -803,7 +831,7 @@ class ComparableTimSort { if (--len2 == 1) break outer; - count2 = len2 - gallopLeft((Comparable) a[cursor1], tmp, 0, len2, len2 - 1); + count2 = len2 - gallopLeft((Comparable) a[cursor1], tmp, tmpBase, len2, len2 - 1); if (count2 != 0) { dest -= count2; cursor2 -= count2; @@ -835,7 +863,7 @@ class ComparableTimSort { } else { assert len1 == 0; assert len2 > 0; - System.arraycopy(tmp, 0, a, dest - (len2 - 1), len2); + System.arraycopy(tmp, tmpBase, a, dest - (len2 - 1), len2); } } @@ -848,7 +876,7 @@ class ComparableTimSort { * @return tmp, whether or not it grew */ private Object[] ensureCapacity(int minCapacity) { - if (tmp.length < minCapacity) { + if (tmpLen < minCapacity) { // Compute smallest power of 2 > minCapacity int newSize = minCapacity; newSize |= newSize >> 1; @@ -863,30 +891,13 @@ class ComparableTimSort { else newSize = Math.min(newSize, a.length >>> 1); + @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) Object[] newArray = new Object[newSize]; tmp = newArray; + tmpLen = newSize; + tmpBase = 0; } return tmp; } - /** - * Checks that fromIndex and toIndex are in range, and throws an - * appropriate exception if they aren't. - * - * @param arrayLen the length of the array - * @param fromIndex the index of the first element of the range - * @param toIndex the index after the last element of the range - * @throws IllegalArgumentException if fromIndex > toIndex - * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 - * or toIndex > arrayLen - */ - private static void rangeCheck(int arrayLen, int fromIndex, int toIndex) { - if (fromIndex > toIndex) - throw new IllegalArgumentException("fromIndex(" + fromIndex + - ") > toIndex(" + toIndex+")"); - if (fromIndex < 0) - throw new ArrayIndexOutOfBoundsException(fromIndex); - if (toIndex > arrayLen) - throw new ArrayIndexOutOfBoundsException(toIndex); - } } diff --git a/src/share/classes/java/util/DualPivotQuicksort.java b/src/share/classes/java/util/DualPivotQuicksort.java index a272bbb2ade010643d0c2c2f39a77efc84f3c762..83f639d5aa3a4770c7fd9ec8df9c54266494e010 100644 --- a/src/share/classes/java/util/DualPivotQuicksort.java +++ b/src/share/classes/java/util/DualPivotQuicksort.java @@ -32,6 +32,11 @@ package java.util; * quicksorts to degrade to quadratic performance, and is typically * faster than traditional (one-pivot) Quicksort implementations. * + * All exposed methods are package-private, designed to be invoked + * from public methods (in class Arrays) after performing any + * necessary array bounds checks and expanding parameters into the + * required forms. + * * @author Vladimir Yaroslavskiy * @author Jon Bentley * @author Josh Bloch @@ -89,22 +94,18 @@ final class DualPivotQuicksort { */ /** - * Sorts the specified array. - * - * @param a the array to be sorted - */ - public static void sort(int[] a) { - sort(a, 0, a.length - 1); - } - - /** - * Sorts the specified range of the array. + * Sorts the specified range of the array using the given + * workspace array slice if possible for merging * * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - public static void sort(int[] a, int left, int right) { + static void sort(int[] a, int left, int right, + int[] work, int workBase, int workLen) { // Use Quicksort on small arrays if (right - left < QUICKSORT_THRESHOLD) { sort(a, left, right, true); @@ -147,24 +148,35 @@ final class DualPivotQuicksort { } // Check special cases + // Implementation note: variable "right" is increased by 1. if (run[count] == right++) { // The last run contains one element run[++count] = right; } else if (count == 1) { // The array is already sorted return; } - /* - * Create temporary array, which is used for merging. - * Implementation note: variable "right" is increased by 1. - */ - int[] b; byte odd = 0; + // Determine alternation base for merge + byte odd = 0; for (int n = 1; (n <<= 1) < count; odd ^= 1); + // Use or create temporary array b for merging + int[] b; // temp array; alternates with a + int ao, bo; // array offsets from 'left' + int blen = right - left; // space needed for b + if (work == null || workLen < blen || workBase + blen > work.length) { + work = new int[blen]; + workBase = 0; + } if (odd == 0) { - b = a; a = new int[b.length]; - for (int i = left - 1; ++i < right; a[i] = b[i]); + System.arraycopy(a, left, work, workBase, blen); + b = a; + bo = 0; + a = work; + ao = workBase - left; } else { - b = new int[a.length]; + b = work; + ao = 0; + bo = workBase - left; } // Merging @@ -172,21 +184,22 @@ final class DualPivotQuicksort { for (int k = (last = 0) + 2; k <= count; k += 2) { int hi = run[k], mi = run[k - 1]; for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) { - if (q >= hi || p < mi && a[p] <= a[q]) { - b[i] = a[p++]; + if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) { + b[i + bo] = a[p++ + ao]; } else { - b[i] = a[q++]; + b[i + bo] = a[q++ + ao]; } } run[++last] = hi; } if ((count & 1) != 0) { for (int i = right, lo = run[count - 1]; --i >= lo; - b[i] = a[i] + b[i + bo] = a[i + ao] ); run[++last] = right; } int[] t = a; a = b; b = t; + int o = ao; ao = bo; bo = o; } } @@ -529,22 +542,18 @@ final class DualPivotQuicksort { } /** - * Sorts the specified array. - * - * @param a the array to be sorted - */ - public static void sort(long[] a) { - sort(a, 0, a.length - 1); - } - - /** - * Sorts the specified range of the array. + * Sorts the specified range of the array using the given + * workspace array slice if possible for merging * * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - public static void sort(long[] a, int left, int right) { + static void sort(long[] a, int left, int right, + long[] work, int workBase, int workLen) { // Use Quicksort on small arrays if (right - left < QUICKSORT_THRESHOLD) { sort(a, left, right, true); @@ -587,24 +596,35 @@ final class DualPivotQuicksort { } // Check special cases + // Implementation note: variable "right" is increased by 1. if (run[count] == right++) { // The last run contains one element run[++count] = right; } else if (count == 1) { // The array is already sorted return; } - /* - * Create temporary array, which is used for merging. - * Implementation note: variable "right" is increased by 1. - */ - long[] b; byte odd = 0; + // Determine alternation base for merge + byte odd = 0; for (int n = 1; (n <<= 1) < count; odd ^= 1); + // Use or create temporary array b for merging + long[] b; // temp array; alternates with a + int ao, bo; // array offsets from 'left' + int blen = right - left; // space needed for b + if (work == null || workLen < blen || workBase + blen > work.length) { + work = new long[blen]; + workBase = 0; + } if (odd == 0) { - b = a; a = new long[b.length]; - for (int i = left - 1; ++i < right; a[i] = b[i]); + System.arraycopy(a, left, work, workBase, blen); + b = a; + bo = 0; + a = work; + ao = workBase - left; } else { - b = new long[a.length]; + b = work; + ao = 0; + bo = workBase - left; } // Merging @@ -612,21 +632,22 @@ final class DualPivotQuicksort { for (int k = (last = 0) + 2; k <= count; k += 2) { int hi = run[k], mi = run[k - 1]; for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) { - if (q >= hi || p < mi && a[p] <= a[q]) { - b[i] = a[p++]; + if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) { + b[i + bo] = a[p++ + ao]; } else { - b[i] = a[q++]; + b[i + bo] = a[q++ + ao]; } } run[++last] = hi; } if ((count & 1) != 0) { for (int i = right, lo = run[count - 1]; --i >= lo; - b[i] = a[i] + b[i + bo] = a[i + ao] ); run[++last] = right; } long[] t = a; a = b; b = t; + int o = ao; ao = bo; bo = o; } } @@ -969,22 +990,18 @@ final class DualPivotQuicksort { } /** - * Sorts the specified array. - * - * @param a the array to be sorted - */ - public static void sort(short[] a) { - sort(a, 0, a.length - 1); - } - - /** - * Sorts the specified range of the array. + * Sorts the specified range of the array using the given + * workspace array slice if possible for merging * * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - public static void sort(short[] a, int left, int right) { + static void sort(short[] a, int left, int right, + short[] work, int workBase, int workLen) { // Use counting sort on large arrays if (right - left > COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR) { int[] count = new int[NUM_SHORT_VALUES]; @@ -1002,7 +1019,7 @@ final class DualPivotQuicksort { } while (--s > 0); } } else { // Use Dual-Pivot Quicksort on small arrays - doSort(a, left, right); + doSort(a, left, right, work, workBase, workLen); } } @@ -1015,8 +1032,12 @@ final class DualPivotQuicksort { * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - private static void doSort(short[] a, int left, int right) { + private static void doSort(short[] a, int left, int right, + short[] work, int workBase, int workLen) { // Use Quicksort on small arrays if (right - left < QUICKSORT_THRESHOLD) { sort(a, left, right, true); @@ -1059,24 +1080,35 @@ final class DualPivotQuicksort { } // Check special cases + // Implementation note: variable "right" is increased by 1. if (run[count] == right++) { // The last run contains one element run[++count] = right; } else if (count == 1) { // The array is already sorted return; } - /* - * Create temporary array, which is used for merging. - * Implementation note: variable "right" is increased by 1. - */ - short[] b; byte odd = 0; + // Determine alternation base for merge + byte odd = 0; for (int n = 1; (n <<= 1) < count; odd ^= 1); + // Use or create temporary array b for merging + short[] b; // temp array; alternates with a + int ao, bo; // array offsets from 'left' + int blen = right - left; // space needed for b + if (work == null || workLen < blen || workBase + blen > work.length) { + work = new short[blen]; + workBase = 0; + } if (odd == 0) { - b = a; a = new short[b.length]; - for (int i = left - 1; ++i < right; a[i] = b[i]); + System.arraycopy(a, left, work, workBase, blen); + b = a; + bo = 0; + a = work; + ao = workBase - left; } else { - b = new short[a.length]; + b = work; + ao = 0; + bo = workBase - left; } // Merging @@ -1084,21 +1116,22 @@ final class DualPivotQuicksort { for (int k = (last = 0) + 2; k <= count; k += 2) { int hi = run[k], mi = run[k - 1]; for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) { - if (q >= hi || p < mi && a[p] <= a[q]) { - b[i] = a[p++]; + if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) { + b[i + bo] = a[p++ + ao]; } else { - b[i] = a[q++]; + b[i + bo] = a[q++ + ao]; } } run[++last] = hi; } if ((count & 1) != 0) { for (int i = right, lo = run[count - 1]; --i >= lo; - b[i] = a[i] + b[i + bo] = a[i + ao] ); run[++last] = right; } short[] t = a; a = b; b = t; + int o = ao; ao = bo; bo = o; } } @@ -1441,22 +1474,18 @@ final class DualPivotQuicksort { } /** - * Sorts the specified array. - * - * @param a the array to be sorted - */ - public static void sort(char[] a) { - sort(a, 0, a.length - 1); - } - - /** - * Sorts the specified range of the array. + * Sorts the specified range of the array using the given + * workspace array slice if possible for merging * * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - public static void sort(char[] a, int left, int right) { + static void sort(char[] a, int left, int right, + char[] work, int workBase, int workLen) { // Use counting sort on large arrays if (right - left > COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR) { int[] count = new int[NUM_CHAR_VALUES]; @@ -1474,7 +1503,7 @@ final class DualPivotQuicksort { } while (--s > 0); } } else { // Use Dual-Pivot Quicksort on small arrays - doSort(a, left, right); + doSort(a, left, right, work, workBase, workLen); } } @@ -1487,8 +1516,12 @@ final class DualPivotQuicksort { * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - private static void doSort(char[] a, int left, int right) { + private static void doSort(char[] a, int left, int right, + char[] work, int workBase, int workLen) { // Use Quicksort on small arrays if (right - left < QUICKSORT_THRESHOLD) { sort(a, left, right, true); @@ -1531,24 +1564,35 @@ final class DualPivotQuicksort { } // Check special cases + // Implementation note: variable "right" is increased by 1. if (run[count] == right++) { // The last run contains one element run[++count] = right; } else if (count == 1) { // The array is already sorted return; } - /* - * Create temporary array, which is used for merging. - * Implementation note: variable "right" is increased by 1. - */ - char[] b; byte odd = 0; + // Determine alternation base for merge + byte odd = 0; for (int n = 1; (n <<= 1) < count; odd ^= 1); + // Use or create temporary array b for merging + char[] b; // temp array; alternates with a + int ao, bo; // array offsets from 'left' + int blen = right - left; // space needed for b + if (work == null || workLen < blen || workBase + blen > work.length) { + work = new char[blen]; + workBase = 0; + } if (odd == 0) { - b = a; a = new char[b.length]; - for (int i = left - 1; ++i < right; a[i] = b[i]); + System.arraycopy(a, left, work, workBase, blen); + b = a; + bo = 0; + a = work; + ao = workBase - left; } else { - b = new char[a.length]; + b = work; + ao = 0; + bo = workBase - left; } // Merging @@ -1556,21 +1600,22 @@ final class DualPivotQuicksort { for (int k = (last = 0) + 2; k <= count; k += 2) { int hi = run[k], mi = run[k - 1]; for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) { - if (q >= hi || p < mi && a[p] <= a[q]) { - b[i] = a[p++]; + if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) { + b[i + bo] = a[p++ + ao]; } else { - b[i] = a[q++]; + b[i + bo] = a[q++ + ao]; } } run[++last] = hi; } if ((count & 1) != 0) { for (int i = right, lo = run[count - 1]; --i >= lo; - b[i] = a[i] + b[i + bo] = a[i + ao] ); run[++last] = right; } char[] t = a; a = b; b = t; + int o = ao; ao = bo; bo = o; } } @@ -1915,15 +1960,6 @@ final class DualPivotQuicksort { /** The number of distinct byte values. */ private static final int NUM_BYTE_VALUES = 1 << 8; - /** - * Sorts the specified array. - * - * @param a the array to be sorted - */ - public static void sort(byte[] a) { - sort(a, 0, a.length - 1); - } - /** * Sorts the specified range of the array. * @@ -1931,7 +1967,7 @@ final class DualPivotQuicksort { * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted */ - public static void sort(byte[] a, int left, int right) { + static void sort(byte[] a, int left, int right) { // Use counting sort on large arrays if (right - left > COUNTING_SORT_THRESHOLD_FOR_BYTE) { int[] count = new int[NUM_BYTE_VALUES]; @@ -1963,22 +1999,18 @@ final class DualPivotQuicksort { } /** - * Sorts the specified array. - * - * @param a the array to be sorted - */ - public static void sort(float[] a) { - sort(a, 0, a.length - 1); - } - - /** - * Sorts the specified range of the array. + * Sorts the specified range of the array using the given + * workspace array slice if possible for merging * * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - public static void sort(float[] a, int left, int right) { + static void sort(float[] a, int left, int right, + float[] work, int workBase, int workLen) { /* * Phase 1: Move NaNs to the end of the array. */ @@ -1997,7 +2029,7 @@ final class DualPivotQuicksort { /* * Phase 2: Sort everything except NaNs (which are already in place). */ - doSort(a, left, right); + doSort(a, left, right, work, workBase, workLen); /* * Phase 3: Place negative zeros before positive zeros. @@ -2064,8 +2096,12 @@ final class DualPivotQuicksort { * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - private static void doSort(float[] a, int left, int right) { + private static void doSort(float[] a, int left, int right, + float[] work, int workBase, int workLen) { // Use Quicksort on small arrays if (right - left < QUICKSORT_THRESHOLD) { sort(a, left, right, true); @@ -2108,24 +2144,35 @@ final class DualPivotQuicksort { } // Check special cases + // Implementation note: variable "right" is increased by 1. if (run[count] == right++) { // The last run contains one element run[++count] = right; } else if (count == 1) { // The array is already sorted return; } - /* - * Create temporary array, which is used for merging. - * Implementation note: variable "right" is increased by 1. - */ - float[] b; byte odd = 0; + // Determine alternation base for merge + byte odd = 0; for (int n = 1; (n <<= 1) < count; odd ^= 1); + // Use or create temporary array b for merging + float[] b; // temp array; alternates with a + int ao, bo; // array offsets from 'left' + int blen = right - left; // space needed for b + if (work == null || workLen < blen || workBase + blen > work.length) { + work = new float[blen]; + workBase = 0; + } if (odd == 0) { - b = a; a = new float[b.length]; - for (int i = left - 1; ++i < right; a[i] = b[i]); + System.arraycopy(a, left, work, workBase, blen); + b = a; + bo = 0; + a = work; + ao = workBase - left; } else { - b = new float[a.length]; + b = work; + ao = 0; + bo = workBase - left; } // Merging @@ -2133,21 +2180,22 @@ final class DualPivotQuicksort { for (int k = (last = 0) + 2; k <= count; k += 2) { int hi = run[k], mi = run[k - 1]; for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) { - if (q >= hi || p < mi && a[p] <= a[q]) { - b[i] = a[p++]; + if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) { + b[i + bo] = a[p++ + ao]; } else { - b[i] = a[q++]; + b[i + bo] = a[q++ + ao]; } } run[++last] = hi; } if ((count & 1) != 0) { for (int i = right, lo = run[count - 1]; --i >= lo; - b[i] = a[i] + b[i + bo] = a[i + ao] ); run[++last] = right; } float[] t = a; a = b; b = t; + int o = ao; ao = bo; bo = o; } } @@ -2490,22 +2538,18 @@ final class DualPivotQuicksort { } /** - * Sorts the specified array. - * - * @param a the array to be sorted - */ - public static void sort(double[] a) { - sort(a, 0, a.length - 1); - } - - /** - * Sorts the specified range of the array. + * Sorts the specified range of the array using the given + * workspace array slice if possible for merging * * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - public static void sort(double[] a, int left, int right) { + static void sort(double[] a, int left, int right, + double[] work, int workBase, int workLen) { /* * Phase 1: Move NaNs to the end of the array. */ @@ -2524,7 +2568,7 @@ final class DualPivotQuicksort { /* * Phase 2: Sort everything except NaNs (which are already in place). */ - doSort(a, left, right); + doSort(a, left, right, work, workBase, workLen); /* * Phase 3: Place negative zeros before positive zeros. @@ -2591,8 +2635,12 @@ final class DualPivotQuicksort { * @param a the array to be sorted * @param left the index of the first element, inclusive, to be sorted * @param right the index of the last element, inclusive, to be sorted + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array */ - private static void doSort(double[] a, int left, int right) { + private static void doSort(double[] a, int left, int right, + double[] work, int workBase, int workLen) { // Use Quicksort on small arrays if (right - left < QUICKSORT_THRESHOLD) { sort(a, left, right, true); @@ -2635,24 +2683,35 @@ final class DualPivotQuicksort { } // Check special cases + // Implementation note: variable "right" is increased by 1. if (run[count] == right++) { // The last run contains one element run[++count] = right; } else if (count == 1) { // The array is already sorted return; } - /* - * Create temporary array, which is used for merging. - * Implementation note: variable "right" is increased by 1. - */ - double[] b; byte odd = 0; + // Determine alternation base for merge + byte odd = 0; for (int n = 1; (n <<= 1) < count; odd ^= 1); + // Use or create temporary array b for merging + double[] b; // temp array; alternates with a + int ao, bo; // array offsets from 'left' + int blen = right - left; // space needed for b + if (work == null || workLen < blen || workBase + blen > work.length) { + work = new double[blen]; + workBase = 0; + } if (odd == 0) { - b = a; a = new double[b.length]; - for (int i = left - 1; ++i < right; a[i] = b[i]); + System.arraycopy(a, left, work, workBase, blen); + b = a; + bo = 0; + a = work; + ao = workBase - left; } else { - b = new double[a.length]; + b = work; + ao = 0; + bo = workBase - left; } // Merging @@ -2660,21 +2719,22 @@ final class DualPivotQuicksort { for (int k = (last = 0) + 2; k <= count; k += 2) { int hi = run[k], mi = run[k - 1]; for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) { - if (q >= hi || p < mi && a[p] <= a[q]) { - b[i] = a[p++]; + if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) { + b[i + bo] = a[p++ + ao]; } else { - b[i] = a[q++]; + b[i + bo] = a[q++ + ao]; } } run[++last] = hi; } if ((count & 1) != 0) { for (int i = right, lo = run[count - 1]; --i >= lo; - b[i] = a[i] + b[i + bo] = a[i + ao] ); run[++last] = right; } double[] t = a; a = b; b = t; + int o = ao; ao = bo; bo = o; } } diff --git a/src/share/classes/java/util/Formatter.java b/src/share/classes/java/util/Formatter.java index 52e9a4a511f518cd2394fea7c50a0a01dc2c36e7..11020fdc411f8c16d67e4a22b28819137a0e699d 100644 --- a/src/share/classes/java/util/Formatter.java +++ b/src/share/classes/java/util/Formatter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -2807,10 +2807,10 @@ public final class Formatter implements Closeable, Flushable { cal = Calendar.getInstance(l == null ? Locale.US : l); cal.setTime((Date)arg); } else if (arg instanceof Calendar) { - cal = (Calendar) ((Calendar)arg).clone(); + cal = (Calendar) ((Calendar) arg).clone(); cal.setLenient(true); } else if (arg instanceof TemporalAccessor) { - print((TemporalAccessor)arg, c, l); + print((TemporalAccessor) arg, c, l); return; } else { failConversion(c, arg); @@ -3242,13 +3242,10 @@ public final class Formatter implements Closeable, Flushable { int prec = (precision == -1 ? 6 : precision); FormattedFloatingDecimal fd - = new FormattedFloatingDecimal(value, prec, - FormattedFloatingDecimal.Form.SCIENTIFIC); + = FormattedFloatingDecimal.valueOf(value, prec, + FormattedFloatingDecimal.Form.SCIENTIFIC); - char[] v = new char[MAX_FD_CHARS]; - int len = fd.getChars(v); - - char[] mant = addZeros(mantissa(v, len), prec); + char[] mant = addZeros(fd.getMantissa(), prec); // If the precision is zero and the '#' flag is set, add the // requested decimal point. @@ -3256,7 +3253,7 @@ public final class Formatter implements Closeable, Flushable { mant = addDot(mant); char[] exp = (value == 0.0) - ? new char[] {'+','0','0'} : exponent(v, len); + ? new char[] {'+','0','0'} : fd.getExponent(); int newW = width; if (width != -1) @@ -3279,15 +3276,10 @@ public final class Formatter implements Closeable, Flushable { int prec = (precision == -1 ? 6 : precision); FormattedFloatingDecimal fd - = new FormattedFloatingDecimal(value, prec, - FormattedFloatingDecimal.Form.DECIMAL_FLOAT); - - // MAX_FD_CHARS + 1 (round?) - char[] v = new char[MAX_FD_CHARS + 1 - + Math.abs(fd.getExponent())]; - int len = fd.getChars(v); + = FormattedFloatingDecimal.valueOf(value, prec, + FormattedFloatingDecimal.Form.DECIMAL_FLOAT); - char[] mant = addZeros(mantissa(v, len), prec); + char[] mant = addZeros(fd.getMantissa(), prec); // If the precision is zero and the '#' flag is set, add the // requested decimal point. @@ -3306,22 +3298,17 @@ public final class Formatter implements Closeable, Flushable { prec = 1; FormattedFloatingDecimal fd - = new FormattedFloatingDecimal(value, prec, - FormattedFloatingDecimal.Form.GENERAL); + = FormattedFloatingDecimal.valueOf(value, prec, + FormattedFloatingDecimal.Form.GENERAL); - // MAX_FD_CHARS + 1 (round?) - char[] v = new char[MAX_FD_CHARS + 1 - + Math.abs(fd.getExponent())]; - int len = fd.getChars(v); - - char[] exp = exponent(v, len); + char[] exp = fd.getExponent(); if (exp != null) { prec -= 1; } else { prec = prec - (value == 0 ? 0 : fd.getExponentRounded()) - 1; } - char[] mant = addZeros(mantissa(v, len), prec); + char[] mant = addZeros(fd.getMantissa(), prec); // If the precision is zero and the '#' flag is set, add the // requested decimal point. if (f.contains(Flags.ALTERNATE) && (prec == 0)) @@ -3380,30 +3367,6 @@ public final class Formatter implements Closeable, Flushable { } } - private char[] mantissa(char[] v, int len) { - int i; - for (i = 0; i < len; i++) { - if (v[i] == 'e') - break; - } - char[] tmp = new char[i]; - System.arraycopy(v, 0, tmp, 0, i); - return tmp; - } - - private char[] exponent(char[] v, int len) { - int i; - for (i = len - 1; i >= 0; i--) { - if (v[i] == 'e') - break; - } - if (i == -1) - return null; - char[] tmp = new char[len - i - 1]; - System.arraycopy(v, i + 1, tmp, 0, len - i - 1); - return tmp; - } - // Add zeros to the requested precision. private char[] addZeros(char[] v, int prec) { // Look for the dot. If we don't find one, the we'll need to add diff --git a/src/share/classes/java/util/HashMap.java b/src/share/classes/java/util/HashMap.java index 5e79498da9fca72bbc50e145319c7643bb19e7e3..c9b106ff75998c7b9ed9fb69ceb3c93c0724a5bb 100644 --- a/src/share/classes/java/util/HashMap.java +++ b/src/share/classes/java/util/HashMap.java @@ -26,6 +26,8 @@ package java.util; import java.io.*; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.function.Consumer; import java.util.function.BiFunction; import java.util.function.Function; @@ -126,7 +128,7 @@ import java.util.function.Function; */ public class HashMap - extends AbstractMap + extends AbstractMap implements Map, Cloneable, Serializable { @@ -150,12 +152,12 @@ public class HashMap /** * An empty table instance to share when the table is not inflated. */ - static final Entry[] EMPTY_TABLE = {}; + static final Object[] EMPTY_TABLE = {}; /** * The table, resized as necessary. Length MUST Always be a power of two. */ - transient Entry[] table = EMPTY_TABLE; + transient Object[] table = EMPTY_TABLE; /** * The number of key-value mappings contained in this map. @@ -186,10 +188,10 @@ public class HashMap */ transient int modCount; + /** + * Holds values which can't be initialized until after VM is booted. + */ private static class Holder { - /** - * - */ static final sun.misc.Unsafe UNSAFE; /** @@ -198,22 +200,616 @@ public class HashMap */ static final long HASHSEED_OFFSET; + static final boolean USE_HASHSEED; + static { - try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - HASHSEED_OFFSET = UNSAFE.objectFieldOffset( - HashMap.class.getDeclaredField("hashSeed")); - } catch (NoSuchFieldException | SecurityException e) { - throw new InternalError("Failed to record hashSeed offset", e); + String hashSeedProp = java.security.AccessController.doPrivileged( + new sun.security.action.GetPropertyAction( + "jdk.map.useRandomSeed")); + boolean localBool = (null != hashSeedProp) + ? Boolean.parseBoolean(hashSeedProp) : false; + USE_HASHSEED = localBool; + + if (USE_HASHSEED) { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + HASHSEED_OFFSET = UNSAFE.objectFieldOffset( + HashMap.class.getDeclaredField("hashSeed")); + } catch (NoSuchFieldException | SecurityException e) { + throw new InternalError("Failed to record hashSeed offset", e); + } + } else { + UNSAFE = null; + HASHSEED_OFFSET = 0; } } } - /** + /* * A randomizing value associated with this instance that is applied to * hash code of keys to make hash collisions harder to find. + * + * Non-final so it can be set lazily, but be sure not to set more than once. + */ + transient final int hashSeed; + + /* + * TreeBin/TreeNode code from CHM doesn't handle the null key. Store the + * null key entry here. + */ + transient Entry nullKeyEntry = null; + + /* + * In order to improve performance under high hash-collision conditions, + * HashMap will switch to storing a bin's entries in a balanced tree + * (TreeBin) instead of a linked-list once the number of entries in the bin + * passes a certain threshold (TreeBin.TREE_THRESHOLD), if at least one of + * the keys in the bin implements Comparable. This technique is borrowed + * from ConcurrentHashMap. + */ + + /* + * Code based on CHMv8 + * + * Node type for TreeBin + */ + final static class TreeNode { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + final HashMap.Entry entry; + + TreeNode(HashMap.Entry entry, Object next, TreeNode parent) { + this.entry = entry; + this.entry.next = next; + this.parent = parent; + } + } + + /** + * Returns a Class for the given object of the form "class C + * implements Comparable", if one exists, else null. See the TreeBin + * docs, below, for explanation. + */ + static Class comparableClassFor(Object x) { + Class c, s, cmpc; Type[] ts, as; Type t; ParameterizedType p; + if ((c = x.getClass()) == String.class) // bypass checks + return c; + if ((cmpc = Comparable.class).isAssignableFrom(c)) { + while (cmpc.isAssignableFrom(s = c.getSuperclass())) + c = s; // find topmost comparable class + if ((ts = c.getGenericInterfaces()) != null) { + for (int i = 0; i < ts.length; ++i) { + if (((t = ts[i]) instanceof ParameterizedType) && + ((p = (ParameterizedType)t).getRawType() == cmpc) && + (as = p.getActualTypeArguments()) != null && + as.length == 1 && as[0] == c) // type arg is c + return c; + } + } + } + return null; + } + + /* + * Code based on CHMv8 + * + * A specialized form of red-black tree for use in bins + * whose size exceeds a threshold. + * + * 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). */ - transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this); + final class TreeBin { + /* + * The bin count threshold for using a tree rather than list for a bin. The + * value reflects the approximate break-even point for using tree-based + * operations. + */ + static final int TREE_THRESHOLD = 16; + + TreeNode root; // root of tree + TreeNode first; // head of next-pointer list + + /* + * Split a TreeBin into lo and hi parts and install in given table. + * + * Existing Entrys are re-used, which maintains the before/after links for + * LinkedHashMap.Entry. + * + * No check for Comparable, though this is the same as CHM. + */ + final void splitTreeBin(Object[] newTable, int i, TreeBin loTree, TreeBin hiTree) { + TreeBin oldTree = this; + int bit = newTable.length >>> 1; + int loCount = 0, hiCount = 0; + TreeNode e = oldTree.first; + TreeNode next; + + // This method is called when the table has just increased capacity, + // so indexFor() is now taking one additional bit of hash into + // account ("bit"). Entries in this TreeBin now belong in one of + // two bins, "i" or "i+bit", depending on if the new top bit of the + // hash is set. The trees for the two bins are loTree and hiTree. + // If either tree ends up containing fewer than TREE_THRESHOLD + // entries, it is converted back to a linked list. + while (e != null) { + // Save entry.next - it will get overwritten in putTreeNode() + next = (TreeNode)e.entry.next; + + int h = e.entry.hash; + K k = (K) e.entry.key; + V v = e.entry.value; + if ((h & bit) == 0) { + ++loCount; + // Re-using e.entry + loTree.putTreeNode(h, k, v, e.entry); + } else { + ++hiCount; + hiTree.putTreeNode(h, k, v, e.entry); + } + // Iterate using the saved 'next' + e = next; + } + if (loCount < TREE_THRESHOLD) { // too small, convert back to list + HashMap.Entry loEntry = null; + TreeNode p = loTree.first; + while (p != null) { + @SuppressWarnings("unchecked") + TreeNode savedNext = (TreeNode) p.entry.next; + p.entry.next = loEntry; + loEntry = p.entry; + p = savedNext; + } + // assert newTable[i] == null; + newTable[i] = loEntry; + } else { + // assert newTable[i] == null; + newTable[i] = loTree; + } + if (hiCount < TREE_THRESHOLD) { // too small, convert back to list + HashMap.Entry hiEntry = null; + TreeNode p = hiTree.first; + while (p != null) { + @SuppressWarnings("unchecked") + TreeNode savedNext = (TreeNode) p.entry.next; + p.entry.next = hiEntry; + hiEntry = p.entry; + p = savedNext; + } + // assert newTable[i + bit] == null; + newTable[i + bit] = hiEntry; + } else { + // assert newTable[i + bit] == null; + newTable[i + bit] = hiTree; + } + } + + /* + * Popuplate the TreeBin with entries from the linked list e + * + * Assumes 'this' is a new/empty TreeBin + * + * Note: no check for Comparable + * Note: I believe this changes iteration order + */ + @SuppressWarnings("unchecked") + void populate(HashMap.Entry e) { + // assert root == null; + // assert first == null; + HashMap.Entry next; + while (e != null) { + // Save entry.next - it will get overwritten in putTreeNode() + next = (HashMap.Entry)e.next; + // Re-using Entry e will maintain before/after in LinkedHM + putTreeNode(e.hash, (K)e.key, (V)e.value, e); + // Iterate using the saved 'next' + e = next; + } + } + + /** + * Copied from CHMv8 + * From CLR + */ + private void rotateLeft(TreeNode p) { + if (p != null) { + TreeNode r = p.right, pp, rl; + if ((rl = p.right = r.left) != null) { + rl.parent = p; + } + if ((pp = r.parent = p.parent) == null) { + root = r; + } else if (pp.left == p) { + pp.left = r; + } else { + pp.right = r; + } + r.left = p; + p.parent = r; + } + } + + /** + * Copied from CHMv8 + * From CLR + */ + private void rotateRight(TreeNode p) { + if (p != null) { + TreeNode l = p.left, pp, lr; + if ((lr = p.left = l.right) != null) { + lr.parent = p; + } + if ((pp = l.parent = p.parent) == null) { + root = l; + } else if (pp.right == p) { + pp.right = l; + } else { + pp.left = l; + } + l.right = p; + p.parent = l; + } + } + + /** + * Returns the TreeNode (or null if not found) for the given + * key. A front-end for recursive version. + */ + final TreeNode getTreeNode(int h, K k) { + return getTreeNode(h, k, root, comparableClassFor(k)); + } + + /** + * Returns the TreeNode (or null if not found) for the given key + * starting at given root. + */ + @SuppressWarnings("unchecked") + final TreeNode getTreeNode (int h, K k, TreeNode p, Class cc) { + // assert k != null; + while (p != null) { + int dir, ph; Object pk; + if ((ph = p.entry.hash) != h) + dir = (h < ph) ? -1 : 1; + else if ((pk = p.entry.key) == k || k.equals(pk)) + return p; + else if (cc == null || comparableClassFor(pk) != cc || + (dir = ((Comparable)k).compareTo(pk)) == 0) { + // assert pk != null; + TreeNode r, pl, pr; // check both sides + if ((pr = p.right) != null && + (r = getTreeNode(h, k, pr, cc)) != null) + return r; + else if ((pl = p.left) != null) + dir = -1; + else // nothing there + break; + } + p = (dir > 0) ? p.right : p.left; + } + return null; + } + + /* + * Finds or adds a node. + * + * 'entry' should be used to recycle an existing Entry (e.g. in the case + * of converting a linked-list bin to a TreeBin). + * If entry is null, a new Entry will be created for the new TreeNode + * + * @return the TreeNode containing the mapping, or null if a new + * TreeNode was added + */ + @SuppressWarnings("unchecked") + TreeNode putTreeNode(int h, K k, V v, HashMap.Entry entry) { + // assert k != null; + //if (entry != null) { + // assert h == entry.hash; + // assert k == entry.key; + // assert v == entry.value; + // } + Class cc = comparableClassFor(k); + TreeNode pp = root, p = null; + int dir = 0; + while (pp != null) { // find existing node or leaf to insert at + int ph; Object pk; + p = pp; + if ((ph = p.entry.hash) != h) + dir = (h < ph) ? -1 : 1; + else if ((pk = p.entry.key) == k || k.equals(pk)) + return p; + else if (cc == null || comparableClassFor(pk) != cc || + (dir = ((Comparable)k).compareTo(pk)) == 0) { + TreeNode r, pr; + if ((pr = p.right) != null && + (r = getTreeNode(h, k, pr, cc)) != null) + return r; + else // continue left + dir = -1; + } + pp = (dir > 0) ? p.right : p.left; + } + + // Didn't find the mapping in the tree, so add it + TreeNode f = first; + TreeNode x; + if (entry != null) { + x = new TreeNode(entry, f, p); + } else { + x = new TreeNode(newEntry(h, k, v, null), f, p); + } + first = x; + + if (p == null) { + root = x; + } else { // attach and rebalance; adapted from CLR + TreeNode xp, xpp; + if (f != null) { + f.prev = x; + } + if (dir <= 0) { + p.left = x; + } else { + p.right = x; + } + x.red = true; + while (x != null && (xp = x.parent) != null && xp.red + && (xpp = xp.parent) != null) { + TreeNode xppl = xpp.left; + if (xp == xppl) { + TreeNode y = xpp.right; + if (y != null && y.red) { + y.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.right) { + rotateLeft(x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + rotateRight(xpp); + } + } + } + } else { + TreeNode y = xppl; + if (y != null && y.red) { + y.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } else { + if (x == xp.left) { + rotateRight(x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + rotateLeft(xpp); + } + } + } + } + } + TreeNode r = root; + if (r != null && r.red) { + r.red = false; + } + } + return null; + } + + /* + * From CHMv8 + * + * 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. + */ + final void deleteTreeNode(TreeNode p) { + TreeNode next = (TreeNode) p.entry.next; // unlink traversal pointers + TreeNode pred = p.prev; + if (pred == null) { + first = next; + } else { + pred.entry.next = next; + } + if (next != null) { + next.prev = pred; + } + TreeNode replacement; + TreeNode pl = p.left; + TreeNode pr = p.right; + if (pl != null && pr != null) { + TreeNode 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 sr = s.right; + TreeNode pp = p.parent; + if (s == pr) { // p was s's direct parent + p.parent = s; + s.right = p; + } else { + TreeNode 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) { + root = s; + } else if (p == pp.left) { + pp.left = s; + } else { + pp.right = s; + } + replacement = sr; + } else { + replacement = (pl != null) ? pl : pr; + } + TreeNode pp = p.parent; + if (replacement == null) { + if (pp == null) { + root = null; + return; + } + replacement = p; + } else { + replacement.parent = pp; + if (pp == null) { + root = replacement; + } else if (p == pp.left) { + pp.left = replacement; + } else { + pp.right = replacement; + } + p.left = p.right = p.parent = null; + } + if (!p.red) { // rebalance, from CLR + TreeNode x = replacement; + while (x != null) { + TreeNode xp, xpl; + if (x.red || (xp = x.parent) == null) { + x.red = false; + break; + } + if (x == (xpl = xp.left)) { + TreeNode sib = xp.right; + if (sib != null && sib.red) { + sib.red = false; + xp.red = true; + rotateLeft(xp); + sib = (xp = x.parent) == null ? null : xp.right; + } + if (sib == null) { + x = xp; + } else { + TreeNode sl = sib.left, sr = sib.right; + if ((sr == null || !sr.red) + && (sl == null || !sl.red)) { + sib.red = true; + x = xp; + } else { + if (sr == null || !sr.red) { + if (sl != null) { + sl.red = false; + } + sib.red = true; + rotateRight(sib); + sib = (xp = x.parent) == null ? + null : xp.right; + } + if (sib != null) { + sib.red = (xp == null) ? false : xp.red; + if ((sr = sib.right) != null) { + sr.red = false; + } + } + if (xp != null) { + xp.red = false; + rotateLeft(xp); + } + x = root; + } + } + } else { // symmetric + TreeNode sib = xpl; + if (sib != null && sib.red) { + sib.red = false; + xp.red = true; + rotateRight(xp); + sib = (xp = x.parent) == null ? null : xp.left; + } + if (sib == null) { + x = xp; + } else { + TreeNode sl = sib.left, sr = sib.right; + if ((sl == null || !sl.red) + && (sr == null || !sr.red)) { + sib.red = true; + x = xp; + } else { + if (sl == null || !sl.red) { + if (sr != null) { + sr.red = false; + } + sib.red = true; + rotateLeft(sib); + sib = (xp = x.parent) == null ? + null : xp.left; + } + if (sib != null) { + sib.red = (xp == null) ? false : xp.red; + if ((sl = sib.left) != null) { + sl.red = false; + } + } + if (xp != null) { + xp.red = false; + rotateRight(xp); + } + x = root; + } + } + } + } + } + if (p == replacement && (pp = p.parent) != null) { + if (p == pp.left) // detach pointers + { + pp.left = null; + } else if (p == pp.right) { + pp.right = null; + } + p.parent = null; + } + } + } /** * Constructs an empty HashMap with the specified initial @@ -233,9 +829,9 @@ public class HashMap if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); - this.loadFactor = loadFactor; threshold = initialCapacity; + hashSeed = initHashSeed(); init(); } @@ -269,10 +865,11 @@ public class HashMap */ public HashMap(Map m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, - DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); + DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); inflateTable(threshold); putAllForCreate(m); + // assert size == m.size(); } private static int roundUpToPowerOf2(int number) { @@ -294,7 +891,7 @@ public class HashMap int capacity = roundUpToPowerOf2(toSize); threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); - table = new Entry[capacity]; + table = new Object[capacity]; } // internal utilities @@ -309,18 +906,25 @@ public class HashMap void init() { } + /** + * Return an initial value for the hashSeed, or 0 if the random seed is not + * enabled. + */ + final int initHashSeed() { + if (sun.misc.VM.isBooted() && Holder.USE_HASHSEED) { + return sun.misc.Hashing.randomHashSeed(this); + } + return 0; + } + /** * Retrieve object hash code and applies a supplemental hash function to the - * result hash, which defends against poor quality hash functions. This is + * result hash, which defends against poor quality hash functions. This is * critical because HashMap uses power-of-two length hash tables, that * otherwise encounter collisions for hashCodes that do not differ * in lower bits. */ final int hash(Object k) { - if (k instanceof String) { - return ((String) k).hash32(); - } - int h = hashSeed ^ k.hashCode(); // This function ensures that hashCodes that differ only by @@ -409,19 +1013,35 @@ public class HashMap if (isEmpty()) { return null; } - - int hash = (key == null) ? 0 : hash(key); - for (Entry e = table[indexFor(hash, table.length)]; - e != null; - e = e.next) { - Object k; - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) - return (Entry)e; + if (key == null) { + return nullKeyEntry; + } + int hash = hash(key); + int bin = indexFor(hash, table.length); + + if (table[bin] instanceof Entry) { + Entry e = (Entry) table[bin]; + for (; e != null; e = (Entry)e.next) { + Object k; + if (e.hash == hash && + ((k = e.key) == key || key.equals(k))) { + return e; + } + } + } else if (table[bin] != null) { + TreeBin e = (TreeBin)table[bin]; + TreeNode p = e.getTreeNode(hash, (K)key); + if (p != null) { + // assert p.entry.hash == hash && p.entry.key.equals(key); + return (Entry)p.entry; + } else { + return null; + } } return null; } + /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old @@ -434,28 +1054,57 @@ public class HashMap * (A null return can also indicate that the map * previously associated null with key.) */ + @SuppressWarnings("unchecked") public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } - if (key == null) + if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") - Entry e = (Entry)table[i]; - for(; e != null; e = e.next) { - Object k; - if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { - V oldValue = e.value; - e.value = value; - e.recordAccess(this); - return oldValue; + boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? + + if (table[i] instanceof Entry) { + // Bin contains ordinary Entries. Search for key in the linked list + // of entries, counting the number of entries. Only check for + // TreeBin conversion if the list size is >= TREE_THRESHOLD. + // (The conversion still may not happen if the table gets resized.) + int listSize = 0; + Entry e = (Entry) table[i]; + for (; e != null; e = (Entry)e.next) { + Object k; + if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { + V oldValue = e.value; + e.value = value; + e.recordAccess(this); + return oldValue; + } + listSize++; + } + // Didn't find, so fall through and call addEntry() to add the + // Entry and check for TreeBin conversion. + checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; + } else if (table[i] != null) { + TreeBin e = (TreeBin)table[i]; + TreeNode p = e.putTreeNode(hash, key, value, null); + if (p == null) { // putTreeNode() added a new node + modCount++; + size++; + if (size >= threshold) { + resize(2 * table.length); + } + return null; + } else { // putTreeNode() found an existing node + Entry pEntry = (Entry)p.entry; + V oldVal = pEntry.value; + pEntry.value = value; + pEntry.recordAccess(this); + return oldVal; } } - modCount++; - addEntry(hash, key, value, i); + addEntry(hash, key, value, i, checkIfNeedTree); return null; } @@ -463,47 +1112,79 @@ public class HashMap * Offloaded version of put for null keys */ private V putForNullKey(V value) { - @SuppressWarnings("unchecked") - Entry e = (Entry)table[0]; - for(; e != null; e = e.next) { - if (e.key == null) { - V oldValue = e.value; - e.value = value; - e.recordAccess(this); - return oldValue; - } + if (nullKeyEntry != null) { + V oldValue = nullKeyEntry.value; + nullKeyEntry.value = value; + nullKeyEntry.recordAccess(this); + return oldValue; } modCount++; - addEntry(0, null, value, 0); + size++; // newEntry() skips size++ + nullKeyEntry = newEntry(0, null, value, null); return null; } + private void putForCreateNullKey(V value) { + // Look for preexisting entry for key. This will never happen for + // clone or deserialize. It will only happen for construction if the + // input Map is a sorted map whose ordering is inconsistent w/ equals. + if (nullKeyEntry != null) { + nullKeyEntry.value = value; + } else { + nullKeyEntry = newEntry(0, null, value, null); + size++; + } + } + + /** * This method is used instead of put by constructors and * pseudoconstructors (clone, readObject). It does not resize the table, - * check for comodification, etc. It calls createEntry rather than - * addEntry. + * check for comodification, etc, though it will convert bins to TreeBins + * as needed. It calls createEntry rather than addEntry. */ + @SuppressWarnings("unchecked") private void putForCreate(K key, V value) { - int hash = null == key ? 0 : hash(key); + if (null == key) { + putForCreateNullKey(value); + return; + } + int hash = hash(key); int i = indexFor(hash, table.length); + boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? /** * Look for preexisting entry for key. This will never happen for * clone or deserialize. It will only happen for construction if the * input Map is a sorted map whose ordering is inconsistent w/ equals. */ - for (@SuppressWarnings("unchecked") - Entry e = (Entry)table[i]; e != null; e = e.next) { - Object k; - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) { - e.value = value; - return; + if (table[i] instanceof Entry) { + int listSize = 0; + Entry e = (Entry) table[i]; + for (; e != null; e = (Entry)e.next) { + Object k; + if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { + e.value = value; + return; + } + listSize++; } + // Didn't find, fall through to createEntry(). + // Check for conversion to TreeBin done via createEntry(). + checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; + } else if (table[i] != null) { + TreeBin e = (TreeBin)table[i]; + TreeNode p = e.putTreeNode(hash, key, value, null); + if (p != null) { + p.entry.setValue(value); // Found an existing node, set value + } else { + size++; // Added a new TreeNode, so update size + } + // don't need modCount++/check for resize - just return + return; } - createEntry(hash, key, value, i); + createEntry(hash, key, value, i, checkIfNeedTree); } private void putAllForCreate(Map m) { @@ -526,14 +1207,14 @@ public class HashMap * is irrelevant). */ void resize(int newCapacity) { - Entry[] oldTable = table; + Object[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } - Entry[] newTable = new Entry[newCapacity]; + Object[] newTable = new Object[newCapacity]; transfer(newTable); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); @@ -541,19 +1222,31 @@ public class HashMap /** * Transfers all entries from current table to newTable. + * + * Assumes newTable is larger than table */ @SuppressWarnings("unchecked") - void transfer(Entry[] newTable) { - Entry[] src = table; + void transfer(Object[] newTable) { + Object[] src = table; + // assert newTable.length > src.length : "newTable.length(" + + // newTable.length + ") expected to be > src.length("+src.length+")"; int newCapacity = newTable.length; - for (int j = 0; j < src.length; j++ ) { - Entry e = (Entry) src[j]; - while(null != e) { - Entry next = e.next; - int i = indexFor(e.hash, newCapacity); - e.next = (Entry) newTable[i]; - newTable[i] = e; - e = next; + for (int j = 0; j < src.length; j++) { + if (src[j] instanceof Entry) { + // Assume: since wasn't TreeBin before, won't need TreeBin now + Entry e = (Entry) src[j]; + while (null != e) { + Entry next = (Entry)e.next; + int i = indexFor(e.hash, newCapacity); + e.next = (Entry) newTable[i]; + newTable[i] = e; + e = next; + } + } else if (src[j] != null) { + TreeBin e = (TreeBin) src[j]; + TreeBin loTree = new TreeBin(); + TreeBin hiTree = new TreeBin(); + e.splitTreeBin(newTable, j, loTree, hiTree); } } Arrays.fill(table, null); @@ -585,20 +1278,13 @@ public class HashMap * By using the conservative calculation, we subject ourself * to at most one extra resize. */ - if (numKeysToBeAdded > threshold) { - int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); - if (targetCapacity > MAXIMUM_CAPACITY) - targetCapacity = MAXIMUM_CAPACITY; - int newCapacity = table.length; - while (newCapacity < targetCapacity) - newCapacity <<= 1; - if (newCapacity > table.length) - resize(newCapacity); + if (numKeysToBeAdded > threshold && table.length < MAXIMUM_CAPACITY) { + resize(table.length * 2); } for (Map.Entry e : m.entrySet()) put(e.getKey(), e.getValue()); - } + } /** * Removes the mapping for the specified key from this map if present. @@ -621,24 +1307,57 @@ public class HashMap if (table == EMPTY_TABLE) { inflateTable(threshold); } - int hash = (key == null) ? 0 : hash(key); + if (key == null) { + if (nullKeyEntry == null || nullKeyEntry.value == null) { + putForNullKey(value); + return null; + } else { + return nullKeyEntry.value; + } + } + int hash = hash(key); int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") - Entry e = (Entry)table[i]; - for(; e != null; e = e.next) { - if (e.hash == hash && Objects.equals(e.key, key)) { - if(e.value != null) { - return e.value; + boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? + + if (table[i] instanceof Entry) { + int listSize = 0; + Entry e = (Entry) table[i]; + for (; e != null; e = (Entry)e.next) { + if (e.hash == hash && Objects.equals(e.key, key)) { + if (e.value != null) { + return e.value; + } + e.value = value; + e.recordAccess(this); + return null; } - e.value = value; + listSize++; + } + // Didn't find, so fall through and call addEntry() to add the + // Entry and check for TreeBin conversion. + checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; + } else if (table[i] != null) { + TreeBin e = (TreeBin)table[i]; + TreeNode p = e.putTreeNode(hash, key, value, null); + if (p == null) { // not found, putTreeNode() added a new node modCount++; - e.recordAccess(this); + size++; + if (size >= threshold) { + resize(2 * table.length); + } return null; + } else { // putTreeNode() found an existing node + Entry pEntry = (Entry)p.entry; + V oldVal = pEntry.value; + if (oldVal == null) { // only replace if maps to null + pEntry.value = value; + pEntry.recordAccess(this); + } + return oldVal; } } - modCount++; - addEntry(hash, key, value, i); + addEntry(hash, key, value, i, checkIfNeedTree); return null; } @@ -647,31 +1366,61 @@ public class HashMap if (isEmpty()) { return false; } - int hash = (key == null) ? 0 : hash(key); + if (key == null) { + if (nullKeyEntry != null && + Objects.equals(nullKeyEntry.value, value)) { + removeNullKey(); + return true; + } + return false; + } + int hash = hash(key); int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") - Entry prev = (Entry)table[i]; - Entry e = prev; - while (e != null) { - Entry next = e.next; - if (e.hash == hash && Objects.equals(e.key, key)) { - if (!Objects.equals(e.value, value)) { - return false; + if (table[i] instanceof Entry) { + @SuppressWarnings("unchecked") + Entry prev = (Entry) table[i]; + Entry e = prev; + while (e != null) { + @SuppressWarnings("unchecked") + Entry next = (Entry) e.next; + if (e.hash == hash && Objects.equals(e.key, key)) { + if (!Objects.equals(e.value, value)) { + return false; + } + modCount++; + size--; + if (prev == e) + table[i] = next; + else + prev.next = next; + e.recordRemoval(this); + return true; + } + prev = e; + e = next; + } + } else if (table[i] != null) { + TreeBin tb = ((TreeBin) table[i]); + TreeNode p = tb.getTreeNode(hash, (K)key); + if (p != null) { + Entry pEntry = (Entry)p.entry; + // assert pEntry.key.equals(key); + if (Objects.equals(pEntry.value, value)) { + modCount++; + size--; + tb.deleteTreeNode(p); + pEntry.recordRemoval(this); + if (tb.root == null || tb.first == null) { + // assert tb.root == null && tb.first == null : + // "TreeBin.first and root should both be null"; + // TreeBin is now empty, we should blank this bin + table[i] = null; + } + return true; } - modCount++; - size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); - return true; } - prev = e; - e = next; } - return false; } @@ -680,39 +1429,82 @@ public class HashMap if (isEmpty()) { return false; } - int hash = (key == null) ? 0 : hash(key); - int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") - Entry e = (Entry)table[i]; - for (; e != null; e = e.next) { - if (e.hash == hash && Objects.equals(e.key, key) && Objects.equals(e.value, oldValue)) { - e.value = newValue; - e.recordAccess(this); + if (key == null) { + if (nullKeyEntry != null && + Objects.equals(nullKeyEntry.value, oldValue)) { + putForNullKey(newValue); return true; } + return false; } + int hash = hash(key); + int i = indexFor(hash, table.length); + if (table[i] instanceof Entry) { + @SuppressWarnings("unchecked") + Entry e = (Entry) table[i]; + for (; e != null; e = (Entry)e.next) { + if (e.hash == hash && Objects.equals(e.key, key) && Objects.equals(e.value, oldValue)) { + e.value = newValue; + e.recordAccess(this); + return true; + } + } + return false; + } else if (table[i] != null) { + TreeBin tb = ((TreeBin) table[i]); + TreeNode p = tb.getTreeNode(hash, key); + if (p != null) { + Entry pEntry = (Entry)p.entry; + // assert pEntry.key.equals(key); + if (Objects.equals(pEntry.value, oldValue)) { + pEntry.value = newValue; + pEntry.recordAccess(this); + return true; + } + } + } return false; } - @Override + @Override public V replace(K key, V value) { if (isEmpty()) { return null; } - int hash = (key == null) ? 0 : hash(key); + if (key == null) { + if (nullKeyEntry != null) { + return putForNullKey(value); + } + return null; + } + int hash = hash(key); int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") - Entry e = (Entry)table[i]; - for (; e != null; e = e.next) { - if (e.hash == hash && Objects.equals(e.key, key)) { - V oldValue = e.value; - e.value = value; - e.recordAccess(this); + if (table[i] instanceof Entry) { + @SuppressWarnings("unchecked") + Entry e = (Entry)table[i]; + for (; e != null; e = (Entry)e.next) { + if (e.hash == hash && Objects.equals(e.key, key)) { + V oldValue = e.value; + e.value = value; + e.recordAccess(this); + return oldValue; + } + } + + return null; + } else if (table[i] != null) { + TreeBin tb = ((TreeBin) table[i]); + TreeNode p = tb.getTreeNode(hash, key); + if (p != null) { + Entry pEntry = (Entry)p.entry; + // assert pEntry.key.equals(key); + V oldValue = pEntry.value; + pEntry.value = value; + pEntry.recordAccess(this); return oldValue; } } - return null; } @@ -721,21 +1513,75 @@ public class HashMap if (table == EMPTY_TABLE) { inflateTable(threshold); } - int hash = (key == null) ? 0 : hash(key); - int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") - Entry e = (Entry)table[i]; - for (; e != null; e = e.next) { - if (e.hash == hash && Objects.equals(e.key, key)) { - V oldValue = e.value; - return oldValue == null ? (e.value = mappingFunction.apply(key)) : oldValue; + if (key == null) { + if (nullKeyEntry == null || nullKeyEntry.value == null) { + V newValue = mappingFunction.apply(key); + if (newValue != null) { + putForNullKey(newValue); + } + return newValue; } + return nullKeyEntry.value; } + int hash = hash(key); + int i = indexFor(hash, table.length); + boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? + if (table[i] instanceof Entry) { + int listSize = 0; + @SuppressWarnings("unchecked") + Entry e = (Entry)table[i]; + for (; e != null; e = (Entry)e.next) { + if (e.hash == hash && Objects.equals(e.key, key)) { + V oldValue = e.value; + if (oldValue == null) { + V newValue = mappingFunction.apply(key); + if (newValue != null) { + e.value = newValue; + e.recordAccess(this); + } + return newValue; + } + return oldValue; + } + listSize++; + } + // Didn't find, fall through to call the mapping function + checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; + } else if (table[i] != null) { + TreeBin e = (TreeBin)table[i]; + V value = mappingFunction.apply(key); + if (value == null) { // Return the existing value, if any + TreeNode p = e.getTreeNode(hash, key); + if (p != null) { + return (V) p.entry.value; + } + return null; + } else { // Put the new value into the Tree, if absent + TreeNode p = e.putTreeNode(hash, key, value, null); + if (p == null) { // not found, new node was added + modCount++; + size++; + if (size >= threshold) { + resize(2 * table.length); + } + return value; + } else { // putTreeNode() found an existing node + Entry pEntry = (Entry)p.entry; + V oldVal = pEntry.value; + if (oldVal == null) { // only replace if maps to null + pEntry.value = value; + pEntry.recordAccess(this); + return value; + } + return oldVal; + } + } + } V newValue = mappingFunction.apply(key); - if (newValue != null) { + if (newValue != null) { // add Entry and check for TreeBin conversion modCount++; - addEntry(hash, key, newValue, i); + addEntry(hash, key, newValue, i, checkIfNeedTree); } return newValue; @@ -746,59 +1592,34 @@ public class HashMap if (isEmpty()) { return null; } - int hash = (key == null) ? 0 : hash(key); - int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") - Entry prev = (Entry)table[i]; - Entry e = prev; - - while (e != null) { - Entry next = e.next; - if (e.hash == hash && Objects.equals(e.key, key)) { - V oldValue = e.value; - if (oldValue == null) - break; + if (key == null) { + V oldValue; + if (nullKeyEntry != null && (oldValue = nullKeyEntry.value) != null) { V newValue = remappingFunction.apply(key, oldValue); - modCount++; - if (newValue == null) { - size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); + if (newValue != null ) { + putForNullKey(newValue); + return newValue; } else { - e.value = newValue; - e.recordAccess(this); + removeNullKey(); } - return newValue; } - prev = e; - e = next; - } - - return null; - } - - @Override - public V compute(K key, BiFunction remappingFunction) { - if (table == EMPTY_TABLE) { - inflateTable(threshold); + return null; } - int hash = (key == null) ? 0 : hash(key); + int hash = hash(key); int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") - Entry prev = (Entry)table[i]; - Entry e = prev; - - while (e != null) { - Entry next = e.next; - if (e.hash == hash && Objects.equals(e.key, key)) { - V oldValue = e.value; - V newValue = remappingFunction.apply(key, oldValue); - if (newValue != oldValue) { - modCount++; + if (table[i] instanceof Entry) { + @SuppressWarnings("unchecked") + Entry prev = (Entry)table[i]; + Entry e = prev; + while (e != null) { + Entry next = (Entry)e.next; + if (e.hash == hash && Objects.equals(e.key, key)) { + V oldValue = e.value; + if (oldValue == null) + break; + V newValue = remappingFunction.apply(key, oldValue); if (newValue == null) { + modCount++; size--; if (prev == e) table[i] = next; @@ -809,17 +1630,136 @@ public class HashMap e.value = newValue; e.recordAccess(this); } + return newValue; + } + prev = e; + e = next; + } + } else if (table[i] != null) { + TreeBin tb = (TreeBin)table[i]; + TreeNode p = tb.getTreeNode(hash, key); + if (p != null) { + Entry pEntry = (Entry)p.entry; + // assert pEntry.key.equals(key); + V oldValue = pEntry.value; + if (oldValue != null) { + V newValue = remappingFunction.apply(key, oldValue); + if (newValue == null) { // remove mapping + modCount++; + size--; + tb.deleteTreeNode(p); + pEntry.recordRemoval(this); + if (tb.root == null || tb.first == null) { + // assert tb.root == null && tb.first == null : + // "TreeBin.first and root should both be null"; + // TreeBin is now empty, we should blank this bin + table[i] = null; + } + } else { + pEntry.value = newValue; + pEntry.recordAccess(this); + } + return newValue; + } + } + } + return null; + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + if (table == EMPTY_TABLE) { + inflateTable(threshold); + } + if (key == null) { + V oldValue = nullKeyEntry == null ? null : nullKeyEntry.value; + V newValue = remappingFunction.apply(key, oldValue); + if (newValue != oldValue) { + if (newValue == null) { + removeNullKey(); + } else { + putForNullKey(newValue); + } + } + return newValue; + } + int hash = hash(key); + int i = indexFor(hash, table.length); + boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? + + if (table[i] instanceof Entry) { + int listSize = 0; + @SuppressWarnings("unchecked") + Entry prev = (Entry)table[i]; + Entry e = prev; + + while (e != null) { + Entry next = (Entry)e.next; + if (e.hash == hash && Objects.equals(e.key, key)) { + V oldValue = e.value; + V newValue = remappingFunction.apply(key, oldValue); + if (newValue != oldValue) { + if (newValue == null) { + modCount++; + size--; + if (prev == e) + table[i] = next; + else + prev.next = next; + e.recordRemoval(this); + } else { + e.value = newValue; + e.recordAccess(this); + } + } + return newValue; + } + prev = e; + e = next; + listSize++; + } + checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; + } else if (table[i] != null) { + TreeBin tb = (TreeBin)table[i]; + TreeNode p = tb.getTreeNode(hash, key); + V oldValue = p == null ? null : (V)p.entry.value; + V newValue = remappingFunction.apply(key, oldValue); + if (newValue != oldValue) { + if (newValue == null) { + Entry pEntry = (Entry)p.entry; + modCount++; + size--; + tb.deleteTreeNode(p); + pEntry.recordRemoval(this); + if (tb.root == null || tb.first == null) { + // assert tb.root == null && tb.first == null : + // "TreeBin.first and root should both be null"; + // TreeBin is now empty, we should blank this bin + table[i] = null; + } + } else { + if (p != null) { // just update the value + Entry pEntry = (Entry)p.entry; + pEntry.value = newValue; + pEntry.recordAccess(this); + } else { // need to put new node + p = tb.putTreeNode(hash, key, newValue, null); + // assert p == null; // should have added a new node + modCount++; + size++; + if (size >= threshold) { + resize(2 * table.length); + } + } } - return newValue; } - prev = e; - e = next; + return newValue; } V newValue = remappingFunction.apply(key, null); if (newValue != null) { modCount++; - addEntry(hash, key, newValue, i); + addEntry(hash, key, newValue, i, checkIfNeedTree); } return newValue; @@ -830,40 +1770,96 @@ public class HashMap if (table == EMPTY_TABLE) { inflateTable(threshold); } - int hash = (key == null) ? 0 : hash(key); + if (key == null) { + V oldValue = nullKeyEntry == null ? null : nullKeyEntry.value; + V newValue = oldValue == null ? value : remappingFunction.apply(oldValue, value); + if (newValue != null) { + putForNullKey(newValue); + } else if (nullKeyEntry != null) { + removeNullKey(); + } + return newValue; + } + int hash = hash(key); int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") - Entry prev = (Entry)table[i]; - Entry e = prev; - - while (e != null) { - Entry next = e.next; - if (e.hash == hash && Objects.equals(e.key, key)) { - V oldValue = e.value; - V newValue = remappingFunction.apply(oldValue, value); - modCount++; - if (newValue == null) { + boolean checkIfNeedTree = false; // Might we convert bin to a TreeBin? + + if (table[i] instanceof Entry) { + int listSize = 0; + @SuppressWarnings("unchecked") + Entry prev = (Entry)table[i]; + Entry e = prev; + + while (e != null) { + Entry next = (Entry)e.next; + if (e.hash == hash && Objects.equals(e.key, key)) { + V oldValue = e.value; + V newValue = (oldValue == null) ? value : + remappingFunction.apply(oldValue, value); + if (newValue == null) { + modCount++; + size--; + if (prev == e) + table[i] = next; + else + prev.next = next; + e.recordRemoval(this); + } else { + e.value = newValue; + e.recordAccess(this); + } + return newValue; + } + prev = e; + e = next; + listSize++; + } + // Didn't find, so fall through and (maybe) call addEntry() to add + // the Entry and check for TreeBin conversion. + checkIfNeedTree = listSize >= TreeBin.TREE_THRESHOLD; + } else if (table[i] != null) { + TreeBin tb = (TreeBin)table[i]; + TreeNode p = tb.getTreeNode(hash, key); + V oldValue = p == null ? null : (V)p.entry.value; + V newValue = (oldValue == null) ? value : + remappingFunction.apply(oldValue, value); + if (newValue == null) { + if (p != null) { + Entry pEntry = (Entry)p.entry; + modCount++; size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); - } else { - e.value = newValue; - e.recordAccess(this); + tb.deleteTreeNode(p); + pEntry.recordRemoval(this); + + if (tb.root == null || tb.first == null) { + // assert tb.root == null && tb.first == null : + // "TreeBin.first and root should both be null"; + // TreeBin is now empty, we should blank this bin + table[i] = null; + } + } + return null; + } else if (newValue != oldValue) { + if (p != null) { // just update the value + Entry pEntry = (Entry)p.entry; + pEntry.value = newValue; + pEntry.recordAccess(this); + } else { // need to put new node + p = tb.putTreeNode(hash, key, newValue, null); + // assert p == null; // should have added a new node + modCount++; + size++; + if (size >= threshold) { + resize(2 * table.length); + } } - return newValue; } - prev = e; - e = next; + return newValue; } - if (value != null) { modCount++; - addEntry(hash, key, value, i); + addEntry(hash, key, value, i, checkIfNeedTree); } - return value; } @@ -873,36 +1869,65 @@ public class HashMap * Removes and returns the entry associated with the specified key * in the HashMap. Returns null if the HashMap contains no mapping * for this key. + * + * We don't bother converting TreeBins back to Entry lists if the bin falls + * back below TREE_THRESHOLD, but we do clear bins when removing the last + * TreeNode in a TreeBin. */ final Entry removeEntryForKey(Object key) { if (isEmpty()) { return null; } - int hash = (key == null) ? 0 : hash(key); + if (key == null) { + if (nullKeyEntry != null) { + return removeNullKey(); + } + return null; + } + int hash = hash(key); int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") + + if (table[i] instanceof Entry) { + @SuppressWarnings("unchecked") Entry prev = (Entry)table[i]; - Entry e = prev; + Entry e = prev; - while (e != null) { - Entry next = e.next; - Object k; - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) { + while (e != null) { + @SuppressWarnings("unchecked") + Entry next = (Entry) e.next; + if (e.hash == hash && Objects.equals(e.key, key)) { + modCount++; + size--; + if (prev == e) + table[i] = next; + else + prev.next = next; + e.recordRemoval(this); + return e; + } + prev = e; + e = next; + } + } else if (table[i] != null) { + TreeBin tb = ((TreeBin) table[i]); + TreeNode p = tb.getTreeNode(hash, (K)key); + if (p != null) { + Entry pEntry = (Entry)p.entry; + // assert pEntry.key.equals(key); modCount++; size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); - return e; + tb.deleteTreeNode(p); + pEntry.recordRemoval(this); + if (tb.root == null || tb.first == null) { + // assert tb.root == null && tb.first == null : + // "TreeBin.first and root should both be null"; + // TreeBin is now empty, we should blank this bin + table[i] = null; + } + return pEntry; } - prev = e; - e = next; } - - return e; + return null; } /** @@ -915,29 +1940,75 @@ public class HashMap Map.Entry entry = (Map.Entry) o; Object key = entry.getKey(); - int hash = (key == null) ? 0 : hash(key); + + if (key == null) { + if (entry.equals(nullKeyEntry)) { + return removeNullKey(); + } + return null; + } + + int hash = hash(key); int i = indexFor(hash, table.length); - @SuppressWarnings("unchecked") - Entry prev = (Entry)table[i]; - Entry e = prev; - while (e != null) { - Entry next = e.next; - if (e.hash == hash && e.equals(entry)) { + if (table[i] instanceof Entry) { + @SuppressWarnings("unchecked") + Entry prev = (Entry)table[i]; + Entry e = prev; + + while (e != null) { + @SuppressWarnings("unchecked") + Entry next = (Entry)e.next; + if (e.hash == hash && e.equals(entry)) { + modCount++; + size--; + if (prev == e) + table[i] = next; + else + prev.next = next; + e.recordRemoval(this); + return e; + } + prev = e; + e = next; + } + } else if (table[i] != null) { + TreeBin tb = ((TreeBin) table[i]); + TreeNode p = tb.getTreeNode(hash, (K)key); + if (p != null && p.entry.equals(entry)) { + @SuppressWarnings("unchecked") + Entry pEntry = (Entry)p.entry; + // assert pEntry.key.equals(key); modCount++; size--; - if (prev == e) - table[i] = next; - else - prev.next = next; - e.recordRemoval(this); - return e; + tb.deleteTreeNode(p); + pEntry.recordRemoval(this); + if (tb.root == null || tb.first == null) { + // assert tb.root == null && tb.first == null : + // "TreeBin.first and root should both be null"; + // TreeBin is now empty, we should blank this bin + table[i] = null; + } + return pEntry; } - prev = e; - e = next; } + return null; + } - return e; + /* + * Remove the mapping for the null key, and update internal accounting + * (size, modcount, recordRemoval, etc). + * + * Assumes nullKeyEntry is non-null. + */ + private Entry removeNullKey() { + // assert nullKeyEntry != null; + Entry retVal = nullKeyEntry; + modCount++; + size--; + retVal.recordRemoval(this); + nullKeyEntry = null; + return retVal; } /** @@ -946,6 +2017,9 @@ public class HashMap */ public void clear() { modCount++; + if (nullKeyEntry != null) { + nullKeyEntry = null; + } Arrays.fill(table, null); size = 0; } @@ -959,27 +2033,58 @@ public class HashMap * specified value */ public boolean containsValue(Object value) { - if (value == null) + if (value == null) { return containsNullValue(); - - Entry[] tab = table; - for (int i = 0; i < tab.length; i++) - for (Entry e = tab[i]; e != null; e = e.next) - if (value.equals(e.value)) - return true; - return false; + } + Object[] tab = table; + for (int i = 0; i < tab.length; i++) { + if (tab[i] instanceof Entry) { + Entry e = (Entry)tab[i]; + for (; e != null; e = (Entry)e.next) { + if (value.equals(e.value)) { + return true; + } + } + } else if (tab[i] != null) { + TreeBin e = (TreeBin)tab[i]; + TreeNode p = e.first; + for (; p != null; p = (TreeNode) p.entry.next) { + if (value == p.entry.value || value.equals(p.entry.value)) { + return true; + } + } + } + } + // Didn't find value in table - could be in nullKeyEntry + return (nullKeyEntry != null && (value == nullKeyEntry.value || + value.equals(nullKeyEntry.value))); } /** * Special-case code for containsValue with null argument */ private boolean containsNullValue() { - Entry[] tab = table; - for (int i = 0; i < tab.length; i++) - for (Entry e = tab[i]; e != null; e = e.next) - if (e.value == null) - return true; - return false; + Object[] tab = table; + for (int i = 0; i < tab.length; i++) { + if (tab[i] instanceof Entry) { + Entry e = (Entry)tab[i]; + for (; e != null; e = (Entry)e.next) { + if (e.value == null) { + return true; + } + } + } else if (tab[i] != null) { + TreeBin e = (TreeBin)tab[i]; + TreeNode p = e.first; + for (; p != null; p = (TreeNode) p.entry.next) { + if (p.entry.value == null) { + return true; + } + } + } + } + // Didn't find value in table - could be in nullKeyEntry + return (nullKeyEntry != null && nullKeyEntry.value == null); } /** @@ -1007,6 +2112,7 @@ public class HashMap result.entrySet = null; result.modCount = 0; result.size = 0; + result.nullKeyEntry = null; result.init(); result.putAllForCreate(this); @@ -1016,13 +2122,13 @@ public class HashMap static class Entry implements Map.Entry { final K key; V value; - Entry next; + Object next; // an Entry, or a TreeNode final int hash; /** * Creates new entry. */ - Entry(int h, K k, V v, Entry n) { + Entry(int h, K k, V v, Object n) { value = v; next = n; key = k; @@ -1054,7 +2160,7 @@ public class HashMap Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; - } + } return false; } @@ -1068,8 +2174,7 @@ public class HashMap /** * This method is invoked whenever the value in an entry is - * overwritten by an invocation of put(k,v) for a key k that's already - * in the HashMap. + * overwritten for a key that's already in the HashMap. */ void recordAccess(HashMap m) { } @@ -1082,50 +2187,96 @@ public class HashMap } } + void addEntry(int hash, K key, V value, int bucketIndex) { + addEntry(hash, key, value, bucketIndex, true); + } + /** * Adds a new entry with the specified key, value and hash code to * the specified bucket. It is the responsibility of this - * method to resize the table if appropriate. + * method to resize the table if appropriate. The new entry is then + * created by calling createEntry(). * * Subclass overrides this to alter the behavior of put method. + * + * If checkIfNeedTree is false, it is known that this bucket will not need + * to be converted to a TreeBin, so don't bothering checking. + * + * Assumes key is not null. */ - void addEntry(int hash, K key, V value, int bucketIndex) { + void addEntry(int hash, K key, V value, int bucketIndex, boolean checkIfNeedTree) { + // assert key != null; if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); - hash = (null != key) ? hash(key) : 0; + hash = hash(key); bucketIndex = indexFor(hash, table.length); } - - createEntry(hash, key, value, bucketIndex); + createEntry(hash, key, value, bucketIndex, checkIfNeedTree); } /** - * Like addEntry except that this version is used when creating entries + * Called by addEntry(), and also used when creating entries * as part of Map construction or "pseudo-construction" (cloning, - * deserialization). This version needn't worry about resizing the table. + * deserialization). This version does not check for resizing of the table. + * + * This method is responsible for converting a bucket to a TreeBin once + * TREE_THRESHOLD is reached. However if checkIfNeedTree is false, it is known + * that this bucket will not need to be converted to a TreeBin, so don't + * bother checking. The new entry is constructed by calling newEntry(). * - * Subclass overrides this to alter the behavior of HashMap(Map), - * clone, and readObject. + * Assumes key is not null. + * + * Note: buckets already converted to a TreeBin don't call this method, but + * instead call TreeBin.putTreeNode() to create new entries. */ - void createEntry(int hash, K key, V value, int bucketIndex) { + void createEntry(int hash, K key, V value, int bucketIndex, boolean checkIfNeedTree) { + // assert key != null; @SuppressWarnings("unchecked") Entry e = (Entry)table[bucketIndex]; - table[bucketIndex] = new Entry<>(hash, key, value, e); + table[bucketIndex] = newEntry(hash, key, value, e); size++; + + if (checkIfNeedTree) { + int listSize = 0; + for (e = (Entry) table[bucketIndex]; e != null; e = (Entry)e.next) { + listSize++; + if (listSize >= TreeBin.TREE_THRESHOLD) { // Convert to TreeBin + if (comparableClassFor(key) != null) { + TreeBin t = new TreeBin(); + t.populate((Entry)table[bucketIndex]); + table[bucketIndex] = t; + } + break; + } + } + } + } + + /* + * Factory method to create a new Entry object. + */ + Entry newEntry(int hash, K key, V value, Object next) { + return new HashMap.Entry<>(hash, key, value, next); } + private abstract class HashIterator implements Iterator { - Entry next; // next entry to return + Object next; // next entry to return, an Entry or a TreeNode int expectedModCount; // For fast-fail int index; // current slot - Entry current; // current entry + Object current; // current entry, an Entry or a TreeNode HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry - Entry[] t = table; - while (index < t.length && (next = t[index++]) == null) - ; + if (nullKeyEntry != null) { + // assert nullKeyEntry.next == null; + // This works with nextEntry(): nullKeyEntry isa Entry, and + // e.next will be null, so we'll hit the findNextBin() call. + next = nullKeyEntry; + } else { + findNextBin(); + } } } @@ -1135,19 +2286,28 @@ public class HashMap @SuppressWarnings("unchecked") final Entry nextEntry() { - if (modCount != expectedModCount) + if (modCount != expectedModCount) { throw new ConcurrentModificationException(); - Entry e = next; + } + Object e = next; + Entry retVal; + if (e == null) throw new NoSuchElementException(); - if ((next = e.next) == null) { - Entry[] t = table; - while (index < t.length && (next = t[index++]) == null) - ; + if (e instanceof Entry) { + retVal = (Entry)e; + next = ((Entry)e).next; + } else { // TreeBin + retVal = (Entry)((TreeNode)e).entry; + next = retVal.next; + } + + if (next == null) { // Move to next bin + findNextBin(); } current = e; - return (Entry)e; + return retVal; } public void remove() { @@ -1155,11 +2315,33 @@ public class HashMap throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); - Object k = current.key; + K k; + + if (current instanceof Entry) { + k = ((Entry)current).key; + } else { + k = ((Entry)((TreeNode)current).entry).key; + + } current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } + + /* + * Set 'next' to the first entry of the next non-empty bin in the table + */ + private void findNextBin() { + // assert next == null; + Object[] t = table; + + while (index < t.length && (next = t[index++]) == null) + ; + if (next instanceof HashMap.TreeBin) { // Point to the first TreeNode + next = ((TreeBin) next).first; + // assert next != null; // There should be no empty TreeBins + } + } } private final class ValueIterator extends HashIterator { @@ -1357,7 +2539,7 @@ public class HashMap if (table==EMPTY_TABLE) { s.writeInt(roundUpToPowerOf2(threshold)); } else { - s.writeInt(table.length); + s.writeInt(table.length); } // Write out size (number of Mappings) @@ -1389,8 +2571,10 @@ public class HashMap } // set other fields that need values - Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET, - sun.misc.Hashing.randomHashSeed(this)); + if (Holder.USE_HASHSEED) { + Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET, + sun.misc.Hashing.randomHashSeed(this)); + } table = EMPTY_TABLE; // Read in number of buckets @@ -1404,9 +2588,9 @@ public class HashMap // capacity chosen by number of mappings and desired load (if >= 0.25) int capacity = (int) Math.min( - mappings * Math.min(1 / loadFactor, 4.0f), - // we have limits... - HashMap.MAXIMUM_CAPACITY); + mappings * Math.min(1 / loadFactor, 4.0f), + // we have limits... + HashMap.MAXIMUM_CAPACITY); // allocate the bucket array; if (mappings > 0) { @@ -1420,9 +2604,9 @@ public class HashMap // Read the keys and values, and put the mappings in the HashMap for (int i=0; i */ static class HashMapSpliterator { final HashMap map; - HashMap.Entry current; // current node + Object current; // current node, can be Entry or TreeNode int index; // current index, modified on advance/split int fence; // one past last index int est; // size estimate int expectedModCount; // for comodification checks + boolean acceptedNull; // Have we accepted the null key? + // Without this, we can't distinguish + // between being at the very beginning (and + // needing to accept null), or being at the + // end of the list in bin 0. In both cases, + // current == null && index == 0. HashMapSpliterator(HashMap m, int origin, int fence, int est, @@ -1450,6 +2640,7 @@ public class HashMap this.fence = fence; this.est = est; this.expectedModCount = expectedModCount; + this.acceptedNull = false; } final int getFence() { // initialize fence and size on first use @@ -1479,9 +2670,15 @@ public class HashMap public KeySpliterator trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; - return (lo >= mid || current != null) ? null : - new KeySpliterator(map, lo, index = mid, est >>>= 1, - expectedModCount); + if (lo >= mid || current != null) { + return null; + } else { + KeySpliterator retVal = new KeySpliterator(map, lo, + index = mid, est >>>= 1, expectedModCount); + // Only 'this' Spliterator chould check for null. + retVal.acceptedNull = true; + return retVal; + } } @SuppressWarnings("unchecked") @@ -1490,21 +2687,39 @@ public class HashMap if (action == null) throw new NullPointerException(); HashMap m = map; - HashMap.Entry[] tab = (HashMap.Entry[])m.table; + Object[] tab = m.table; if ((hi = fence) < 0) { mc = expectedModCount = m.modCount; hi = fence = tab.length; } else mc = expectedModCount; - if (tab.length >= hi && (i = index) >= 0 && i < (index = hi)) { - HashMap.Entry p = current; + + if (!acceptedNull) { + acceptedNull = true; + if (m.nullKeyEntry != null) { + action.accept(m.nullKeyEntry.key); + } + } + if (tab.length >= hi && (i = index) >= 0 && + (i < (index = hi) || current != null)) { + Object p = current; + current = null; do { - if (p == null) + if (p == null) { p = tab[i++]; - else { - action.accept(p.getKey()); - p = p.next; + if (p instanceof HashMap.TreeBin) { + p = ((HashMap.TreeBin)p).first; + } + } else { + HashMap.Entry entry; + if (p instanceof HashMap.Entry) { + entry = (HashMap.Entry)p; + } else { + entry = (HashMap.Entry)((TreeNode)p).entry; + } + action.accept(entry.key); + p = entry.next; } } while (p != null || i < hi); if (m.modCount != mc) @@ -1517,14 +2732,34 @@ public class HashMap int hi; if (action == null) throw new NullPointerException(); - HashMap.Entry[] tab = (HashMap.Entry[])map.table; - if (tab.length >= (hi = getFence()) && index >= 0) { + Object[] tab = map.table; + hi = getFence(); + + if (!acceptedNull) { + acceptedNull = true; + if (map.nullKeyEntry != null) { + action.accept(map.nullKeyEntry.key); + if (map.modCount != expectedModCount) + throw new ConcurrentModificationException(); + return true; + } + } + if (tab.length >= hi && index >= 0) { while (current != null || index < hi) { - if (current == null) + if (current == null) { current = tab[index++]; - else { - K k = current.getKey(); - current = current.next; + if (current instanceof HashMap.TreeBin) { + current = ((HashMap.TreeBin)current).first; + } + } else { + HashMap.Entry entry; + if (current instanceof HashMap.Entry) { + entry = (HashMap.Entry)current; + } else { + entry = (HashMap.Entry)((TreeNode)current).entry; + } + K k = entry.key; + current = entry.next; action.accept(k); if (map.modCount != expectedModCount) throw new ConcurrentModificationException(); @@ -1551,9 +2786,15 @@ public class HashMap public ValueSpliterator trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; - return (lo >= mid || current != null) ? null : - new ValueSpliterator(map, lo, index = mid, est >>>= 1, - expectedModCount); + if (lo >= mid || current != null) { + return null; + } else { + ValueSpliterator retVal = new ValueSpliterator(map, + lo, index = mid, est >>>= 1, expectedModCount); + // Only 'this' Spliterator chould check for null. + retVal.acceptedNull = true; + return retVal; + } } @SuppressWarnings("unchecked") @@ -1562,21 +2803,39 @@ public class HashMap if (action == null) throw new NullPointerException(); HashMap m = map; - HashMap.Entry[] tab = (HashMap.Entry[])m.table; + Object[] tab = m.table; if ((hi = fence) < 0) { mc = expectedModCount = m.modCount; hi = fence = tab.length; } else mc = expectedModCount; - if (tab.length >= hi && (i = index) >= 0 && i < (index = hi)) { - HashMap.Entry p = current; + + if (!acceptedNull) { + acceptedNull = true; + if (m.nullKeyEntry != null) { + action.accept(m.nullKeyEntry.value); + } + } + if (tab.length >= hi && (i = index) >= 0 && + (i < (index = hi) || current != null)) { + Object p = current; + current = null; do { - if (p == null) + if (p == null) { p = tab[i++]; - else { - action.accept(p.getValue()); - p = p.next; + if (p instanceof HashMap.TreeBin) { + p = ((HashMap.TreeBin)p).first; + } + } else { + HashMap.Entry entry; + if (p instanceof HashMap.Entry) { + entry = (HashMap.Entry)p; + } else { + entry = (HashMap.Entry)((TreeNode)p).entry; + } + action.accept(entry.value); + p = entry.next; } } while (p != null || i < hi); if (m.modCount != mc) @@ -1589,14 +2848,34 @@ public class HashMap int hi; if (action == null) throw new NullPointerException(); - HashMap.Entry[] tab = (HashMap.Entry[])map.table; - if (tab.length >= (hi = getFence()) && index >= 0) { + Object[] tab = map.table; + hi = getFence(); + + if (!acceptedNull) { + acceptedNull = true; + if (map.nullKeyEntry != null) { + action.accept(map.nullKeyEntry.value); + if (map.modCount != expectedModCount) + throw new ConcurrentModificationException(); + return true; + } + } + if (tab.length >= hi && index >= 0) { while (current != null || index < hi) { - if (current == null) + if (current == null) { current = tab[index++]; - else { - V v = current.getValue(); - current = current.next; + if (current instanceof HashMap.TreeBin) { + current = ((HashMap.TreeBin)current).first; + } + } else { + HashMap.Entry entry; + if (current instanceof HashMap.Entry) { + entry = (Entry)current; + } else { + entry = (Entry)((TreeNode)current).entry; + } + V v = entry.value; + current = entry.next; action.accept(v); if (map.modCount != expectedModCount) throw new ConcurrentModificationException(); @@ -1622,9 +2901,15 @@ public class HashMap public EntrySpliterator trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; - return (lo >= mid || current != null) ? null : - new EntrySpliterator(map, lo, index = mid, est >>>= 1, - expectedModCount); + if (lo >= mid || current != null) { + return null; + } else { + EntrySpliterator retVal = new EntrySpliterator(map, + lo, index = mid, est >>>= 1, expectedModCount); + // Only 'this' Spliterator chould check for null. + retVal.acceptedNull = true; + return retVal; + } } @SuppressWarnings("unchecked") @@ -1633,21 +2918,40 @@ public class HashMap if (action == null) throw new NullPointerException(); HashMap m = map; - HashMap.Entry[] tab = (HashMap.Entry[])m.table; + Object[] tab = m.table; if ((hi = fence) < 0) { mc = expectedModCount = m.modCount; hi = fence = tab.length; } else mc = expectedModCount; - if (tab.length >= hi && (i = index) >= 0 && i < (index = hi)) { - HashMap.Entry p = current; + + if (!acceptedNull) { + acceptedNull = true; + if (m.nullKeyEntry != null) { + action.accept(m.nullKeyEntry); + } + } + if (tab.length >= hi && (i = index) >= 0 && + (i < (index = hi) || current != null)) { + Object p = current; + current = null; do { - if (p == null) + if (p == null) { p = tab[i++]; - else { - action.accept(p); - p = p.next; + if (p instanceof HashMap.TreeBin) { + p = ((HashMap.TreeBin)p).first; + } + } else { + HashMap.Entry entry; + if (p instanceof HashMap.Entry) { + entry = (HashMap.Entry)p; + } else { + entry = (HashMap.Entry)((TreeNode)p).entry; + } + action.accept(entry); + p = entry.next; + } } while (p != null || i < hi); if (m.modCount != mc) @@ -1660,14 +2964,33 @@ public class HashMap int hi; if (action == null) throw new NullPointerException(); - HashMap.Entry[] tab = (HashMap.Entry[])map.table; - if (tab.length >= (hi = getFence()) && index >= 0) { + Object[] tab = map.table; + hi = getFence(); + + if (!acceptedNull) { + acceptedNull = true; + if (map.nullKeyEntry != null) { + action.accept(map.nullKeyEntry); + if (map.modCount != expectedModCount) + throw new ConcurrentModificationException(); + return true; + } + } + if (tab.length >= hi && index >= 0) { while (current != null || index < hi) { - if (current == null) + if (current == null) { current = tab[index++]; - else { - HashMap.Entry e = current; - current = current.next; + if (current instanceof HashMap.TreeBin) { + current = ((HashMap.TreeBin)current).first; + } + } else { + HashMap.Entry e; + if (current instanceof HashMap.Entry) { + e = (Entry)current; + } else { + e = (Entry)((TreeNode)current).entry; + } + current = e.next; action.accept(e); if (map.modCount != expectedModCount) throw new ConcurrentModificationException(); diff --git a/src/share/classes/java/util/Hashtable.java b/src/share/classes/java/util/Hashtable.java index 1e38fcaa43bdb3b79f8a317e84e4e82218ecceeb..a078aa3e207656b5176b3c4b0633fc13db3ee05f 100644 --- a/src/share/classes/java/util/Hashtable.java +++ b/src/share/classes/java/util/Hashtable.java @@ -180,13 +180,27 @@ public class Hashtable */ static final long HASHSEED_OFFSET; + static final boolean USE_HASHSEED; + static { - try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - HASHSEED_OFFSET = UNSAFE.objectFieldOffset( - Hashtable.class.getDeclaredField("hashSeed")); - } catch (NoSuchFieldException | SecurityException e) { - throw new InternalError("Failed to record hashSeed offset", e); + String hashSeedProp = java.security.AccessController.doPrivileged( + new sun.security.action.GetPropertyAction( + "jdk.map.useRandomSeed")); + boolean localBool = (null != hashSeedProp) + ? Boolean.parseBoolean(hashSeedProp) : false; + USE_HASHSEED = localBool; + + if (USE_HASHSEED) { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + HASHSEED_OFFSET = UNSAFE.objectFieldOffset( + Hashtable.class.getDeclaredField("hashSeed")); + } catch (NoSuchFieldException | SecurityException e) { + throw new InternalError("Failed to record hashSeed offset", e); + } + } else { + UNSAFE = null; + HASHSEED_OFFSET = 0; } } } @@ -194,21 +208,24 @@ public class Hashtable /** * A randomizing value associated with this instance that is applied to * hash code of keys to make hash collisions harder to find. + * + * Non-final so it can be set lazily, but be sure not to set more than once. */ - transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this); + transient final int hashSeed; - private int hash(Object k) { - if (k instanceof String) { - return ((String)k).hash32(); + /** + * Return an initial value for the hashSeed, or 0 if the random seed is not + * enabled. + */ + final int initHashSeed() { + if (sun.misc.VM.isBooted() && Holder.USE_HASHSEED) { + return sun.misc.Hashing.randomHashSeed(this); } + return 0; + } - int h = hashSeed ^ k.hashCode(); - - // This function ensures that hashCodes that differ only by - // constant multiples at each bit position have a bounded - // number of collisions (approximately 8 at default load factor). - h ^= (h >>> 20) ^ (h >>> 12); - return h ^ (h >>> 7) ^ (h >>> 4); + private int hash(Object k) { + return hashSeed ^ k.hashCode(); } /** @@ -232,6 +249,7 @@ public class Hashtable this.loadFactor = loadFactor; table = new Entry[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); + hashSeed = initHashSeed(); } /** @@ -1187,8 +1205,10 @@ public class Hashtable s.defaultReadObject(); // set hashMask - Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET, - sun.misc.Hashing.randomHashSeed(this)); + if (Holder.USE_HASHSEED) { + Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET, + sun.misc.Hashing.randomHashSeed(this)); + } // Read the original length of the array and number of elements int origlength = s.readInt(); diff --git a/src/share/classes/java/util/IntSummaryStatistics.java b/src/share/classes/java/util/IntSummaryStatistics.java index f179e67478ba4c2fa0ac710673f4b51df7ecee5e..fcca3296f85569b9462c80b0cc554b382fde924a 100644 --- a/src/share/classes/java/util/IntSummaryStatistics.java +++ b/src/share/classes/java/util/IntSummaryStatistics.java @@ -159,7 +159,7 @@ public class IntSummaryStatistics implements IntConsumer { */ public String toString() { return String.format( - "%s{count=%d, sum=%d, min=%d, average=%d, max=%d}", + "%s{count=%d, sum=%d, min=%d, average=%f, max=%d}", this.getClass().getSimpleName(), getCount(), getSum(), diff --git a/src/share/classes/java/util/LinkedHashMap.java b/src/share/classes/java/util/LinkedHashMap.java index 1693db6c68816ee4f428f10d68990fc65488625f..100710b1bdd67398fc9af1e35adb3f42165f75ac 100644 --- a/src/share/classes/java/util/LinkedHashMap.java +++ b/src/share/classes/java/util/LinkedHashMap.java @@ -55,9 +55,9 @@ import java.io.*; * order they were presented.) * *

A special {@link #LinkedHashMap(int,float,boolean) constructor} is - * provided to create a linked hash map whose order of iteration is the order - * in which its entries were last accessed, from least-recently accessed to - * most-recently (access-order). This kind of map is well-suited to + * provided to create a LinkedHashMap whose order of iteration is the + * order in which its entries were last accessed, from least-recently accessed + * to most-recently (access-order). This kind of map is well-suited to * building LRU caches. Invoking the put or get method * results in an access to the corresponding entry (assuming it exists after * the invocation completes). The putAll method generates one entry @@ -242,23 +242,6 @@ public class LinkedHashMap header.before = header.after = header; } - /** - * Transfers all entries to new table array. This method is called - * by superclass resize. It is overridden for performance, as it is - * faster to iterate using our linked list. - */ - @Override - @SuppressWarnings("unchecked") - void transfer(HashMap.Entry[] newTable) { - int newCapacity = newTable.length; - for (Entry e = header.after; e != header; e = e.after) { - int index = indexFor(e.hash, newCapacity); - e.next = (HashMap.Entry)newTable[index]; - newTable[index] = e; - } - } - - /** * Returns true if this map maps one or more keys to the * specified value. @@ -320,7 +303,7 @@ public class LinkedHashMap // These fields comprise the doubly linked list used for iteration. Entry before, after; - Entry(int hash, K key, V value, HashMap.Entry next) { + Entry(int hash, K key, V value, Object next) { super(hash, key, value, next); } @@ -344,7 +327,7 @@ public class LinkedHashMap /** * This method is invoked by the superclass whenever the value - * of a pre-existing entry is read by Map.get or modified by Map.set. + * of a pre-existing entry is read by Map.get or modified by Map.put. * If the enclosing Map is access-ordered, it moves the entry * to the end of the list; otherwise, it does nothing. */ @@ -422,8 +405,9 @@ public class LinkedHashMap * allocated entry to get inserted at the end of the linked list and * removes the eldest entry if appropriate. */ - void addEntry(int hash, K key, V value, int bucketIndex) { - super.addEntry(hash, key, value, bucketIndex); + @Override + void addEntry(int hash, K key, V value, int bucketIndex, boolean checkIfNeedTree) { + super.addEntry(hash, key, value, bucketIndex, checkIfNeedTree); // Remove eldest entry if instructed Entry eldest = header.after; @@ -432,17 +416,14 @@ public class LinkedHashMap } } - /** - * This override differs from addEntry in that it doesn't resize the - * table or remove the eldest entry. + /* + * Create a new LinkedHashMap.Entry and setup the before/after pointers */ - void createEntry(int hash, K key, V value, int bucketIndex) { - @SuppressWarnings("unchecked") - HashMap.Entry old = (HashMap.Entry)table[bucketIndex]; - Entry e = new Entry<>(hash, key, value, old); - table[bucketIndex] = e; - e.addBefore(header); - size++; + @Override + HashMap.Entry newEntry(int hash, K key, V value, Object next) { + Entry newEntry = new Entry<>(hash, key, value, next); + newEntry.addBefore(header); + return newEntry; } /** diff --git a/src/share/classes/java/util/ListResourceBundle.java b/src/share/classes/java/util/ListResourceBundle.java index 736016f7f840d3d710846227b4fca20536e37998..9aa7fdba550d132e661365447cd5b492b157bdbd 100644 --- a/src/share/classes/java/util/ListResourceBundle.java +++ b/src/share/classes/java/util/ListResourceBundle.java @@ -89,7 +89,7 @@ import sun.util.ResourceBundleEnumeration; * * public class MyResources_fr extends ListResourceBundle { * protected Object[][] getContents() { - * return new Object[][] = { + * return new Object[][] { * // LOCALIZE THIS * {"s1", "Le disque \"{1}\" {0}."}, // MessageFormat pattern * {"s2", "1"}, // location of {0} in pattern diff --git a/src/share/classes/java/util/LongSummaryStatistics.java b/src/share/classes/java/util/LongSummaryStatistics.java index 3c7b7aee5610a2156908c202132350137892fd0c..0e2da71f8bc95d30f0b16ec71a1f417e20effe89 100644 --- a/src/share/classes/java/util/LongSummaryStatistics.java +++ b/src/share/classes/java/util/LongSummaryStatistics.java @@ -171,7 +171,7 @@ public class LongSummaryStatistics implements LongConsumer, IntConsumer { */ public String toString() { return String.format( - "%s{count=%d, sum=%d, min=%d, average=%d, max=%d}", + "%s{count=%d, sum=%d, min=%d, average=%f, max=%d}", this.getClass().getSimpleName(), getCount(), getSum(), diff --git a/src/share/classes/java/util/PrimitiveIterator.java b/src/share/classes/java/util/PrimitiveIterator.java index d4e032e7430d01ac05001a04b94cf934eeeaa17a..f05d9e0660527f87ac650d57fdd7be88d9b40ea4 100644 --- a/src/share/classes/java/util/PrimitiveIterator.java +++ b/src/share/classes/java/util/PrimitiveIterator.java @@ -91,6 +91,7 @@ public interface PrimitiveIterator extends Iterator { * @throws NullPointerException if the specified action is null */ default void forEachRemaining(IntConsumer action) { + Objects.requireNonNull(action); while (hasNext()) action.accept(nextInt()); } @@ -123,6 +124,8 @@ public interface PrimitiveIterator extends Iterator { forEachRemaining((IntConsumer) action); } else { + // The method reference action::accept is never null + Objects.requireNonNull(action); if (Tripwire.ENABLED) Tripwire.trip(getClass(), "{0} calling PrimitiveIterator.OfInt.forEachRemainingInt(action::accept)"); forEachRemaining((IntConsumer) action::accept); @@ -162,6 +165,7 @@ public interface PrimitiveIterator extends Iterator { * @throws NullPointerException if the specified action is null */ default void forEachRemaining(LongConsumer action) { + Objects.requireNonNull(action); while (hasNext()) action.accept(nextLong()); } @@ -194,6 +198,8 @@ public interface PrimitiveIterator extends Iterator { forEachRemaining((LongConsumer) action); } else { + // The method reference action::accept is never null + Objects.requireNonNull(action); if (Tripwire.ENABLED) Tripwire.trip(getClass(), "{0} calling PrimitiveIterator.OfLong.forEachRemainingLong(action::accept)"); forEachRemaining((LongConsumer) action::accept); @@ -232,6 +238,7 @@ public interface PrimitiveIterator extends Iterator { * @throws NullPointerException if the specified action is null */ default void forEachRemaining(DoubleConsumer action) { + Objects.requireNonNull(action); while (hasNext()) action.accept(nextDouble()); } @@ -265,6 +272,8 @@ public interface PrimitiveIterator extends Iterator { forEachRemaining((DoubleConsumer) action); } else { + // The method reference action::accept is never null + Objects.requireNonNull(action); if (Tripwire.ENABLED) Tripwire.trip(getClass(), "{0} calling PrimitiveIterator.OfDouble.forEachRemainingDouble(action::accept)"); forEachRemaining((DoubleConsumer) action::accept); diff --git a/src/share/classes/java/util/PropertyResourceBundle.java b/src/share/classes/java/util/PropertyResourceBundle.java index 98ebd3b0f419241a4a3844133d42076d0bac0df3..841d7d759a26e3a4270dc4b16e5137f751ad3bd3 100644 --- a/src/share/classes/java/util/PropertyResourceBundle.java +++ b/src/share/classes/java/util/PropertyResourceBundle.java @@ -124,6 +124,8 @@ public class PropertyResourceBundle extends ResourceBundle { * to read from. * @throws IOException if an I/O error occurs * @throws NullPointerException if stream is null + * @throws IllegalArgumentException if {@code stream} contains a + * malformed Unicode escape sequence. */ @SuppressWarnings({"unchecked", "rawtypes"}) public PropertyResourceBundle (InputStream stream) throws IOException { @@ -142,6 +144,8 @@ public class PropertyResourceBundle extends ResourceBundle { * read from. * @throws IOException if an I/O error occurs * @throws NullPointerException if reader is null + * @throws IllegalArgumentException if a malformed Unicode escape sequence appears + * from {@code reader}. * @since 1.6 */ @SuppressWarnings({"unchecked", "rawtypes"}) diff --git a/src/share/classes/java/util/Spliterator.java b/src/share/classes/java/util/Spliterator.java index 588b8176d4741c0973a7ac8bf040b973cbc0b156..10c551a592119cb0b457fcec325933e45b67ac79 100644 --- a/src/share/classes/java/util/Spliterator.java +++ b/src/share/classes/java/util/Spliterator.java @@ -140,32 +140,32 @@ import java.util.function.LongConsumer; * (in approximate order of decreasing desirability): *

    *
  • The source cannot be structurally interfered with. - *
    For example, an instance of + *
    For example, an instance of * {@link java.util.concurrent.CopyOnWriteArrayList} is an immutable source. * A Spliterator created from the source reports a characteristic of * {@code IMMUTABLE}.
  • *
  • The source manages concurrent modifications. - *
    For example, a key set of a {@link java.util.concurrent.ConcurrentHashMap} + *
    For example, a key set of a {@link java.util.concurrent.ConcurrentHashMap} * is a concurrent source. A Spliterator created from the source reports a * characteristic of {@code CONCURRENT}.
  • *
  • The mutable source provides a late-binding and fail-fast Spliterator. - *
    Late binding narrows the window during which interference can affect + *
    Late binding narrows the window during which interference can affect * the calculation; fail-fast detects, on a best-effort basis, that structural * interference has occurred after traversal has commenced and throws * {@link ConcurrentModificationException}. For example, {@link ArrayList}, * and many other non-concurrent {@code Collection} classes in the JDK, provide * a late-binding, fail-fast spliterator.
  • *
  • The mutable source provides a non-late-binding but fail-fast Spliterator. - *
    The source increases the likelihood of throwing + *
    The source increases the likelihood of throwing * {@code ConcurrentModificationException} since the window of potential * interference is larger.
  • *
  • The mutable source provides a late-binding and non-fail-fast Spliterator. - *
    The source risks arbitrary, non-deterministic behavior after traversal + *
    The source risks arbitrary, non-deterministic behavior after traversal * has commenced since interference is not detected. *
  • *
  • The mutable source provides a non-late-binding and non-fail-fast * Spliterator. - *
    The source increases the risk of arbitrary, non-deterministic behavior + *
    The source increases the risk of arbitrary, non-deterministic behavior * since non-detected interference may occur after construction. *
  • *
@@ -284,6 +284,8 @@ import java.util.function.LongConsumer; * is set to {@code true} then diagnostic warnings are reported if boxing of * primitive values occur when operating on primitive subtype specializations. * + * @param the type of elements returned by this Spliterator + * * @see Collection * @since 1.8 */ @@ -333,9 +335,8 @@ public interface Spliterator { * Upon non-null return: *