diff --git a/src/share/classes/java/lang/Class.java b/src/share/classes/java/lang/Class.java index df4d457367d662cd134d69173e45e3b47ad0e0e9..05cfa88cad1851b4c63b920a1b66d87903c58308 100644 --- a/src/share/classes/java/lang/Class.java +++ b/src/share/classes/java/lang/Class.java @@ -360,36 +360,24 @@ public final class Class implements java.io.Serializable, * any exception thrown by the constructor in a (checked) {@link * java.lang.reflect.InvocationTargetException}. * - * @return a newly allocated instance of the class represented by this - * object. - * @exception IllegalAccessException if the class or its nullary - * constructor is not accessible. - * @exception InstantiationException - * if this {@code Class} represents an abstract class, - * an interface, an array class, a primitive type, or void; - * or if the class has no nullary constructor; - * or if the instantiation fails for some other reason. - * @exception ExceptionInInitializerError if the initialization - * provoked by this method fails. - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - * - * + * @return a newly allocated instance of the class represented by this + * object. + * @throws IllegalAccessException if the class or its nullary + * constructor is not accessible. + * @throws InstantiationException + * if this {@code Class} represents an abstract class, + * an interface, an array class, a primitive type, or void; + * or if the class has no nullary constructor; + * or if the instantiation fails for some other reason. + * @throws ExceptionInInitializerError if the initialization + * provoked by this method fails. + * @throws SecurityException + * If a security manager, s, is present and + * the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class. */ @CallerSensitive public T newInstance() @@ -981,24 +969,27 @@ public final class Class implements java.io.Serializable, * * @return the immediately enclosing method of the underlying class, if * that class is a local or anonymous class; otherwise {@code null}. - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: * - * * @since 1.5 */ @CallerSensitive @@ -1025,11 +1016,6 @@ public final class Class implements java.io.Serializable, // Perform access check Class enclosingCandidate = enclosingInfo.getEnclosingClass(); - // be very careful not to change the stack depth of this - // checkMemberAccess call for security reasons - // see java.lang.SecurityManager.checkMemberAccess - // - // Note that we need to do this on the enclosing class enclosingCandidate.checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true); /* @@ -1137,24 +1123,26 @@ public final class Class implements java.io.Serializable, * * @return the immediately enclosing constructor of the underlying class, if * that class is a local or anonymous class; otherwise {@code null}. - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: + * @throws SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: * - *
    + *
      * - *
    • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(enclosingClass, Member.DECLARED)} denies - * access to the constructors within the enclosing class + *
    • the caller's class loader is not the same as the + * class loader of the enclosing class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the constructors within the enclosing class * - *
    • the caller's class loader is not the same as or an - * ancestor of the class loader for the enclosing class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of the enclosing class + *
    • the caller's class loader is not the same as or an + * ancestor of the class loader for the enclosing class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of the enclosing class * - *
    + *
* @since 1.5 */ @CallerSensitive @@ -1180,11 +1168,6 @@ public final class Class implements java.io.Serializable, // Perform access check Class enclosingCandidate = enclosingInfo.getEnclosingClass(); - // be very careful not to change the stack depth of this - // checkMemberAccess call for security reasons - // see java.lang.SecurityManager.checkMemberAccess - // - // Note that we need to do this on the enclosing class enclosingCandidate.checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true); /* @@ -1457,25 +1440,14 @@ public final class Class implements java.io.Serializable, * class, or void. * * @return the array of {@code Class} objects representing the public - * members of this class - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - *
    - * - *
  • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.PUBLIC)} method - * denies access to the classes within this class - * - *
  • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class - * - *
+ * members of this class + * @throws SecurityException + * If a security manager, s, is present and + * the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class. * * @since JDK1.1 */ @@ -1530,25 +1502,14 @@ public final class Class implements java.io.Serializable, *

See The Java Language Specification, sections 8.2 and 8.3. * * @return the array of {@code Field} objects representing the - * public fields - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - *

    - * - *
  • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.PUBLIC)} denies - * access to the fields within this class - * - *
  • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class - * - *
+ * public fields + * @throws SecurityException + * If a security manager, s, is present and + * the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class. * * @since JDK1.1 */ @@ -1579,25 +1540,14 @@ public final class Class implements java.io.Serializable, *

See The Java Language Specification, sections 8.2 and 8.4. * * @return the array of {@code Method} objects representing the - * public methods of this class - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - *

    - * - *
  • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.PUBLIC)} denies - * access to the methods within this class - * - *
  • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class - * - *
+ * public methods of this class + * @throws SecurityException + * If a security manager, s, is present and + * the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class. * * @since JDK1.1 */ @@ -1626,25 +1576,14 @@ public final class Class implements java.io.Serializable, * {@code Constructor[]}. * * @return the array of {@code Constructor} objects representing the - * public constructors of this class - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - *
    - * - *
  • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.PUBLIC)} denies - * access to the constructors within this class - * - *
  • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class - * - *
+ * public constructors of this class + * @throws SecurityException + * If a security manager, s, is present and + * the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class. * * @since JDK1.1 */ @@ -1678,29 +1617,18 @@ public final class Class implements java.io.Serializable, *

See The Java Language Specification, sections 8.2 and 8.3. * * @param name the field name - * @return the {@code Field} object of this class specified by - * {@code name} - * @exception NoSuchFieldException if a field with the specified name is - * not found. - * @exception NullPointerException if {@code name} is {@code null} - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - *

    - * - *
  • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.PUBLIC)} denies - * access to the field - * - *
  • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class - * - *
+ * @return the {@code Field} object of this class specified by + * {@code name} + * @throws NoSuchFieldException if a field with the specified name is + * not found. + * @throws NullPointerException if {@code name} is {@code null} + * @throws SecurityException + * If a security manager, s, is present and + * the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class. * * @since JDK1.1 */ @@ -1762,28 +1690,17 @@ public final class Class implements java.io.Serializable, * @param name the name of the method * @param parameterTypes the list of parameters * @return the {@code Method} object that matches the specified - * {@code name} and {@code parameterTypes} - * @exception NoSuchMethodException if a matching method is not found - * or if the name is "<init>"or "<clinit>". - * @exception NullPointerException if {@code name} is {@code null} - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - *
    - * - *
  • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.PUBLIC)} denies - * access to the method - * - *
  • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class - * - *
+ * {@code name} and {@code parameterTypes} + * @throws NoSuchMethodException if a matching method is not found + * or if the name is "<init>"or "<clinit>". + * @throws NullPointerException if {@code name} is {@code null} + * @throws SecurityException + * If a security manager, s, is present and + * the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class. * * @since JDK1.1 */ @@ -1816,26 +1733,15 @@ public final class Class implements java.io.Serializable, * * @param parameterTypes the parameter array * @return the {@code Constructor} object of the public constructor that - * matches the specified {@code parameterTypes} - * @exception NoSuchMethodException if a matching method is not found. - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - *
    - * - *
  • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.PUBLIC)} denies - * access to the constructor - * - *
  • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class - * - *
+ * matches the specified {@code parameterTypes} + * @throws NoSuchMethodException if a matching method is not found. + * @throws SecurityException + * If a security manager, s, is present and + * the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class. * * @since JDK1.1 */ @@ -1858,25 +1764,27 @@ public final class Class implements java.io.Serializable, * primitive type, an array class, or void. * * @return the array of {@code Class} objects representing all the - * declared members of this class - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: + * declared members of this class + * @throws SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: * - *
    + *
      * - *
    • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.DECLARED)} denies - * access to the declared classes within this class + *
    • the caller's class loader is not the same as the + * class loader of this class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the declared classes within this class * - *
    • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class + *
    • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class * - *
    + *
* * @since JDK1.1 */ @@ -1899,26 +1807,28 @@ public final class Class implements java.io.Serializable, * *

See The Java Language Specification, sections 8.2 and 8.3. * - * @return the array of {@code Field} objects representing all the - * declared fields of this class - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: + * @return the array of {@code Field} objects representing all the + * declared fields of this class + * @throws SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: * - *

    + *
      * - *
    • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.DECLARED)} denies - * access to the declared fields within this class + *
    • the caller's class loader is not the same as the + * class loader of this class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the declared fields within this class * - *
    • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class + *
    • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class * - *
    + *
* * @since JDK1.1 */ @@ -1945,26 +1855,28 @@ public final class Class implements java.io.Serializable, * *

See The Java Language Specification, section 8.2. * - * @return the array of {@code Method} objects representing all the - * declared methods of this class - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: + * @return the array of {@code Method} objects representing all the + * declared methods of this class + * @throws SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: * - *

    + *
      * - *
    • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.DECLARED)} denies - * access to the declared methods within this class + *
    • the caller's class loader is not the same as the + * class loader of this class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the declared methods within this class * - *
    • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class + *
    • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class * - *
    + *
* * @since JDK1.1 */ @@ -1988,26 +1900,28 @@ public final class Class implements java.io.Serializable, * *

See The Java Language Specification, section 8.2. * - * @return the array of {@code Constructor} objects representing all the - * declared constructors of this class - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: + * @return the array of {@code Constructor} objects representing all the + * declared constructors of this class + * @throws SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: * - *

    + *
      * - *
    • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.DECLARED)} denies - * access to the declared constructors within this class + *
    • the caller's class loader is not the same as the + * class loader of this class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the declared constructors within this class * - *
    • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class + *
    • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class * - *
    + *
* * @since JDK1.1 */ @@ -2026,29 +1940,31 @@ public final class Class implements java.io.Serializable, * will not reflect the {@code length} field of an array class. * * @param name the name of the field - * @return the {@code Field} object for the specified field in this - * class - * @exception NoSuchFieldException if a field with the specified name is - * not found. - * @exception NullPointerException if {@code name} is {@code null} - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - *
    - * - *
  • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.DECLARED)} denies - * access to the declared field - * - *
  • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class - * - *
+ * @return the {@code Field} object for the specified field in this + * class + * @throws NoSuchFieldException if a field with the specified name is + * not found. + * @throws NullPointerException if {@code name} is {@code null} + * @throws SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *
    + * + *
  • the caller's class loader is not the same as the + * class loader of this class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the declared field + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
* * @since JDK1.1 */ @@ -2080,28 +1996,30 @@ public final class Class implements java.io.Serializable, * * @param name the name of the method * @param parameterTypes the parameter array - * @return the {@code Method} object for the method of this class - * matching the specified name and parameters - * @exception NoSuchMethodException if a matching method is not found. - * @exception NullPointerException if {@code name} is {@code null} - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - *
    - * - *
  • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.DECLARED)} denies - * access to the declared method - * - *
  • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class - * - *
+ * @return the {@code Method} object for the method of this class + * matching the specified name and parameters + * @throws NoSuchMethodException if a matching method is not found. + * @throws NullPointerException if {@code name} is {@code null} + * @throws SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *
    + * + *
  • the caller's class loader is not the same as the + * class loader of this class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the declared method + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
* * @since JDK1.1 */ @@ -2129,27 +2047,29 @@ public final class Class implements java.io.Serializable, * include the explicit enclosing instance as the first parameter. * * @param parameterTypes the parameter array - * @return The {@code Constructor} object for the constructor with the - * specified parameter list - * @exception NoSuchMethodException if a matching method is not found. - * @exception SecurityException - * If a security manager, s, is present and any of the - * following conditions is met: - * - *
    - * - *
  • invocation of - * {@link SecurityManager#checkMemberAccess - * s.checkMemberAccess(this, Member.DECLARED)} denies - * access to the declared constructor - * - *
  • the caller's class loader is not the same as or an - * ancestor of the class loader for the current class and - * invocation of {@link SecurityManager#checkPackageAccess - * s.checkPackageAccess()} denies access to the package - * of this class - * - *
+ * @return The {@code Constructor} object for the constructor with the + * specified parameter list + * @throws NoSuchMethodException if a matching method is not found. + * @throws SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *
    + * + *
  • the caller's class loader is not the same as the + * class loader of this class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the declared constructor + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
* * @since JDK1.1 */ @@ -2306,14 +2226,6 @@ public final class Class implements java.io.Serializable, */ static native Class getPrimitiveClass(String name); - private static boolean isCheckMemberAccessOverridden(SecurityManager smgr) { - if (smgr.getClass() == SecurityManager.class) return false; - - Class[] paramTypes = new Class[] {Class.class, int.class}; - return smgr.getClass().getMethod0("checkMemberAccess", paramTypes). - getDeclaringClass() != SecurityManager.class; - } - /* * Check if client is allowed to access members. If access is denied, * throw a SecurityException. @@ -2326,19 +2238,17 @@ public final class Class implements java.io.Serializable, private void checkMemberAccess(int which, Class caller, boolean checkProxyInterfaces) { final SecurityManager s = System.getSecurityManager(); if (s != null) { + /* Default policy allows access to all {@link Member#PUBLIC} members, + * as well as access to classes that have the same class loader as the caller. + * In all other cases, it requires RuntimePermission("accessDeclaredMembers") + * permission. + */ final ClassLoader ccl = ClassLoader.getClassLoader(caller); final ClassLoader cl = getClassLoader0(); - if (!isCheckMemberAccessOverridden(s)) { - // Inlined SecurityManager.checkMemberAccess - if (which != Member.PUBLIC) { - if (ccl != cl) { - s.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); - } + if (which != Member.PUBLIC) { + if (ccl != cl) { + s.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); } - } else { - // Don't refactor; otherwise break the stack depth for - // checkMemberAccess of subclasses of SecurityManager as specified. - s.checkMemberAccess(this, which); } this.checkPackageAccess(ccl, checkProxyInterfaces); } diff --git a/src/share/classes/java/lang/SecurityManager.java b/src/share/classes/java/lang/SecurityManager.java index ca187630528ac5589ff9b32f9b6ccfd3bcc530de..34be905bd02752b9374f29d781b10603bcdc18aa 100644 --- a/src/share/classes/java/lang/SecurityManager.java +++ b/src/share/classes/java/lang/SecurityManager.java @@ -1675,10 +1675,18 @@ class SecurityManager { * permission to access members. * @exception NullPointerException if the clazz argument is * null. + * + * @deprecated This method relies on the caller being at a stack depth + * of 4 which is error-prone and cannot be enforced by the runtime. + * Users of this method should instead invoke {@link #checkPermission} + * directly. This method will be changed in a future release + * to check the permission {@code java.security.AllPermission}. + * * @see java.lang.reflect.Member * @since JDK1.1 * @see #checkPermission(java.security.Permission) checkPermission */ + @Deprecated @CallerSensitive public void checkMemberAccess(Class clazz, int which) { if (clazz == null) { diff --git a/src/share/classes/java/lang/instrument/Instrumentation.java b/src/share/classes/java/lang/instrument/Instrumentation.java index 1844837fd3b2d6172dff9699a1c31d2399fdd88b..4cdb2de9c9aac0819d1d7d4ae0eefb3ce5bc48d7 100644 --- a/src/share/classes/java/lang/instrument/Instrumentation.java +++ b/src/share/classes/java/lang/instrument/Instrumentation.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 @@ -363,6 +363,8 @@ public interface Instrumentation { * Primitive classes (for example, java.lang.Integer.TYPE) * and array classes are never modifiable. * + * @param theClass the class to check for being modifiable + * @return whether or not the argument class is modifiable * @throws java.lang.NullPointerException if the specified class is null. * * @see #retransformClasses @@ -549,14 +551,14 @@ public interface Instrumentation { * {@link java.lang.instrument.ClassFileTransformer ClassFileTransformer}, * it enables native methods to be * instrumented. - *

+ *

* Since native methods cannot be directly instrumented * (they have no bytecodes), they must be wrapped with * a non-native method which can be instrumented. * For example, if we had: *

      *   native boolean foo(int x);
- *

+ *

* We could transform the class file (with the * ClassFileTransformer during the initial definition * of the class) so that this becomes: @@ -567,14 +569,14 @@ public interface Instrumentation { * } * * native boolean wrapped_foo(int x); - *

+ *

* Where foo becomes a wrapper for the actual native * method with the appended prefix "wrapped_". Note that * "wrapped_" would be a poor choice of prefix since it * might conceivably form the name of an existing method * thus something like "$$$MyAgentWrapped$$$_" would be * better but would make these examples less readable. - *

+ *

* The wrapper will allow data to be collected on the native * method call, but now the problem becomes linking up the * wrapped method with the native implementation. @@ -583,7 +585,7 @@ public interface Instrumentation { * which might be: *

      *   Java_somePackage_someClass_foo(JNIEnv* env, jint x)
- *

+ *

* This function allows the prefix to be specified and the * proper resolution to occur. * Specifically, when the standard resolution fails, the @@ -596,29 +598,29 @@ public interface Instrumentation { *

{@code
      *   method(foo) -> nativeImplementation(foo)
      * }
- *

+ *

* When this fails, the resolution will be retried with * the specified prefix prepended to the method name, * yielding the correct resolution: *

{@code
      *   method(wrapped_foo) -> nativeImplementation(foo)
      * }
- *

+ *

* For automatic resolution, the JVM will attempt: *

{@code
      *   method(wrapped_foo) -> nativeImplementation(wrapped_foo)
      * }
- *

+ *

* When this fails, the resolution will be retried with * the specified prefix deleted from the implementation name, * yielding the correct resolution: *

{@code
      *   method(wrapped_foo) -> nativeImplementation(foo)
      * }
- *

+ *

* Note that since the prefix is only used when standard * resolution fails, native methods can be wrapped selectively. - *

+ *

* Since each ClassFileTransformer * can do its own transformation of the bytecodes, more * than one layer of wrappers may be applied. Thus each diff --git a/src/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/share/classes/java/lang/invoke/MethodHandleImpl.java index 8efbda8061442d3938ba4cc7e1a0b03e146c7dd5..04eda964966f2e25e8c816f50ce83af9befbb5ec 100644 --- a/src/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -747,7 +747,8 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; GuardWithCatch gguard = new GuardWithCatch(gtarget, exType, gcatcher); if (gtarget == null || gcatcher == null) throw new InternalError(); MethodHandle ginvoker = GuardWithCatch.VARARGS_INVOKE.bindReceiver(gguard); - return makeCollectArguments(ginvoker, ValueConversions.varargsArray(nargs), 0, false); + MethodHandle gcollect = makeCollectArguments(ginvoker, ValueConversions.varargsArray(nargs), 0, false); + return makePairwiseConvert(gcollect, type, 2); } } diff --git a/src/share/classes/java/lang/invoke/MethodHandles.java b/src/share/classes/java/lang/invoke/MethodHandles.java index 3bf24bc85039cd16630bd7bde128af127532eea0..78b0121563626796346acf37d80ea76bc3b898f8 100644 --- a/src/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/share/classes/java/lang/invoke/MethodHandles.java @@ -41,6 +41,7 @@ import sun.reflect.misc.ReflectUtil; import sun.security.util.SecurityConstants; import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandleNatives.Constants.*; +import sun.security.util.SecurityConstants; /** * This class consists exclusively of static methods that operate on or return @@ -305,36 +306,30 @@ public class MethodHandles { * * If a security manager is present, member lookups are subject to * additional checks. - * From one to four calls are made to the security manager. + * From one to three calls are made to the security manager. * Any of these calls can refuse access by throwing a * {@link java.lang.SecurityException SecurityException}. * Define {@code smgr} as the security manager, + * {@code lookc} as the lookup class of the current lookup object, * {@code refc} as the containing class in which the member * is being sought, and {@code defc} as the class in which the * member is actually defined. + * The value {@code lookc} is defined as not present + * if the current lookup object does not have + * {@linkplain java.lang.invoke.MethodHandles.Lookup#PRIVATE private access}. * The calls are made according to the following rules: *

    - *
  • In all cases, {@link SecurityManager#checkMemberAccess - * smgr.checkMemberAccess(refc, Member.PUBLIC)} is called. - *
  • If the class loader of the lookup class is not + *
  • If {@code lookc} is not present, or if its class loader is not * the same as or an ancestor of the class loader of {@code refc}, * then {@link SecurityManager#checkPackageAccess * smgr.checkPackageAccess(refcPkg)} is called, * where {@code refcPkg} is the package of {@code refc}. + *
  • If the retrieved member is not public and + * {@code lookc} is not present, then + * {@link SecurityManager#checkPermission smgr.checkPermission} + * with {@code RuntimePermission("accessDeclaredMembers")} is called. *
  • If the retrieved member is not public, - * {@link SecurityManager#checkMemberAccess - * smgr.checkMemberAccess(defc, Member.DECLARED)} is called. - * (Note that {@code defc} might be the same as {@code refc}.) - * The default implementation of this security manager method - * inspects the stack to determine the original caller of - * the reflective request (such as {@code findStatic}), - * and performs additional permission checks if the - * class loader of {@code defc} differs from the class - * loader of the class from which the reflective request came. - *
  • If the retrieved member is not public, - * and if {@code defc} and {@code refc} are in different class loaders, - * and if the class loader of the lookup class is not - * the same as or an ancestor of the class loader of {@code defc}, + * and if {@code defc} and {@code refc} are different, * then {@link SecurityManager#checkPackageAccess * smgr.checkPackageAccess(defcPkg)} is called, * where {@code defcPkg} is the package of {@code defc}. @@ -1053,22 +1048,6 @@ return mh1; return (allowedModes & PRIVATE) != 0; } - /** - * Determine whether a security manager has an overridden - * SecurityManager.checkMemberAccess method. - */ - private boolean isCheckMemberAccessOverridden(SecurityManager sm) { - final Class cls = sm.getClass(); - if (cls == SecurityManager.class) return false; - - try { - return cls.getMethod("checkMemberAccess", Class.class, int.class). - getDeclaringClass() != SecurityManager.class; - } catch (NoSuchMethodException e) { - throw new InternalError("should not reach here"); - } - } - /** * Perform necessary access checks. * Determines a trustable caller class to compare with refc, the symbolic reference class. @@ -1079,45 +1058,22 @@ return mh1; if (smgr == null) return; if (allowedModes == TRUSTED) return; - final boolean overridden = isCheckMemberAccessOverridden(smgr); // Step 1: - { - // Default policy is to allow Member.PUBLIC; no need to check - // permission if SecurityManager is the default implementation - final int which = Member.PUBLIC; - final Class clazz = refc; - if (overridden) { - // Don't refactor; otherwise break the stack depth for - // checkMemberAccess of subclasses of SecurityManager as specified. - smgr.checkMemberAccess(clazz, which); - } - } - - // Step 2: if (!isFullPowerLookup() || !VerifyAccess.classLoaderIsAncestor(lookupClass, refc)) { ReflectUtil.checkPackageAccess(refc); } - // Step 3: + // Step 2: if (m.isPublic()) return; Class defc = m.getDeclaringClass(); { - // Inline SecurityManager.checkMemberAccess - final int which = Member.DECLARED; - final Class clazz = defc; - if (!overridden) { - if (!isFullPowerLookup()) { - smgr.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); - } - } else { - // Don't refactor; otherwise break the stack depth for - // checkMemberAccess of subclasses of SecurityManager as specified. - smgr.checkMemberAccess(clazz, which); + if (!isFullPowerLookup()) { + smgr.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); } } - // Step 4: + // Step 3: if (defc != refc) { ReflectUtil.checkPackageAccess(defc); } diff --git a/src/share/classes/java/lang/reflect/Member.java b/src/share/classes/java/lang/reflect/Member.java index 5d3ab3dd8b30e5dc97ae39151840b091381a9ca1..a539cb546143f5d924a0518854e1e0bfc738a027 100644 --- a/src/share/classes/java/lang/reflect/Member.java +++ b/src/share/classes/java/lang/reflect/Member.java @@ -42,14 +42,12 @@ interface Member { /** * Identifies the set of all public members of a class or interface, * including inherited members. - * @see java.lang.SecurityManager#checkMemberAccess */ public static final int PUBLIC = 0; /** * Identifies the set of declared members of a class or interface. * Inherited members are not included. - * @see java.lang.SecurityManager#checkMemberAccess */ public static final int DECLARED = 1; diff --git a/src/share/classes/java/math/BigInteger.java b/src/share/classes/java/math/BigInteger.java index 48354a9559c2aa805bd300befdc555203c9329c5..6569fcb1796b25bfe8f9fa843107986b5f9b2bf5 100644 --- a/src/share/classes/java/math/BigInteger.java +++ b/src/share/classes/java/math/BigInteger.java @@ -1042,7 +1042,7 @@ public class BigInteger extends Number implements Comparable { * recalculate powers of radix^(2^n) more than once. This speeds * Schoenhage recursive base conversion significantly. */ - private static ArrayList[] powerCache; + private static volatile BigInteger[][] powerCache; /** The cache of logarithms of radices for base conversion. */ private static final double[] logCache; @@ -1063,14 +1063,12 @@ public class BigInteger extends Number implements Comparable { * with just the very first value. Additional values will be created * on demand. */ - powerCache = (ArrayList[]) - new ArrayList[Character.MAX_RADIX+1]; + powerCache = new BigInteger[Character.MAX_RADIX+1][]; logCache = new double[Character.MAX_RADIX+1]; for (int i=Character.MIN_RADIX; i<=Character.MAX_RADIX; i++) { - powerCache[i] = new ArrayList(1); - powerCache[i].add(BigInteger.valueOf(i)); + powerCache[i] = new BigInteger[] { BigInteger.valueOf(i) }; logCache[i] = Math.log(i); } } @@ -3454,22 +3452,25 @@ public class BigInteger extends Number implements Comparable { * This could be changed to a more complicated caching method using * Future. */ - private static synchronized BigInteger getRadixConversionCache(int radix, - int exponent) { - BigInteger retVal = null; - ArrayList cacheLine = powerCache[radix]; - int oldSize = cacheLine.size(); - if (exponent >= oldSize) { - cacheLine.ensureCapacity(exponent+1); - for (int i=oldSize; i<=exponent; i++) { - retVal = cacheLine.get(i-1).square(); - cacheLine.add(i, retVal); - } + private static BigInteger getRadixConversionCache(int radix, int exponent) { + BigInteger[] cacheLine = powerCache[radix]; // volatile read + if (exponent < cacheLine.length) { + return cacheLine[exponent]; } - else - retVal = cacheLine.get(exponent); - return retVal; + int oldLength = cacheLine.length; + cacheLine = Arrays.copyOf(cacheLine, exponent + 1); + for (int i = oldLength; i <= exponent; i++) { + cacheLine[i] = cacheLine[i - 1].pow(2); + } + + BigInteger[][] pc = powerCache; // volatile read again + if (exponent >= pc[radix].length) { + pc = pc.clone(); + pc[radix] = cacheLine; + powerCache = pc; // volatile write, publish + } + return cacheLine[exponent]; } /* zero[i] is a string of i consecutive zeros. */ diff --git a/src/share/classes/java/security/KeyStore.java b/src/share/classes/java/security/KeyStore.java index 68df16ed113f76913b46dde51a6fa3cae71a9b17..c363d0719f7ea603e59d2b1f7cd39e48e60d6850 100644 --- a/src/share/classes/java/security/KeyStore.java +++ b/src/share/classes/java/security/KeyStore.java @@ -227,15 +227,13 @@ public class KeyStore { * {@link #store(KeyStore.LoadStoreParameter) store} operations. *

    * The following syntax is supported for configuration data: - *

    -     *
    +     * 
    {@code
          *     domain  [ ...] {
          *         keystore  [ ...] ;
          *         ...
          *     };
          *     ...
    -     *
    -     * 
    + * }
    * where {@code domainName} and {@code keystoreName} are identifiers * and {@code property} is a key/value pairing. The key and value are * separated by an 'equals' symbol and the value is enclosed in double diff --git a/src/share/classes/java/security/Provider.java b/src/share/classes/java/security/Provider.java index 54cf9327d1d12d420b5e48093ea4c15e4a6ab805..89a7b492669280bd10ab6e0fb337d70527efd476 100644 --- a/src/share/classes/java/security/Provider.java +++ b/src/share/classes/java/security/Provider.java @@ -67,6 +67,7 @@ import java.lang.reflect.*; * or modified by applications. * The following attributes are automatically placed in each Provider object: * + * * * * diff --git a/src/share/classes/java/security/Security.java b/src/share/classes/java/security/Security.java index c3dd716058d08ddd8573dfebf7fecd7b58edc4f7..98699da8149c4e1e3eeadf91553c3071d51d875a 100644 --- a/src/share/classes/java/security/Security.java +++ b/src/share/classes/java/security/Security.java @@ -480,8 +480,8 @@ public final class Security { * Returns an array containing all installed providers that satisfy the * specified selection criterion, or null if no such providers have been * installed. The returned providers are ordered - * according to their preference order. + * according to their + * {@linkplain #insertProviderAt(java.security.Provider, int) preference order}. * *

    A cryptographic service is always associated with a particular * algorithm or type. For example, a digital signature service is @@ -492,8 +492,8 @@ public final class Security { *

    The selection criterion must be specified in one of the following two * formats: *

      - *
    • <crypto_service>.<algorithm_or_type>

      The - * cryptographic service name must not contain any dots. + *

    • {@literal .} + *

      The cryptographic service name must not contain any dots. *

      A * provider satisfies the specified selection criterion iff the provider * implements the @@ -501,11 +501,12 @@ public final class Security { *

      For example, "CertificateFactory.X.509" * would be satisfied by any provider that supplied * a CertificateFactory implementation for X.509 certificates. - *

    • <crypto_service>.<algorithm_or_type> - * <attribute_name>:< attribute_value> + *
    • {@literal . + * :} *

      The cryptographic service name must not contain any dots. There * must be one or more space charaters between the - * <algorithm_or_type> and the <attribute_name>. + * {@literal } and the + * {@literal }. *

      A provider satisfies this selection criterion iff the * provider implements the specified algorithm or type for the specified * cryptographic service and its implementation meets the @@ -558,8 +559,9 @@ public final class Security { * Returns an array containing all installed providers that satisfy the * specified* selection criteria, or null if no such providers have been * installed. The returned providers are ordered - * according to their preference order. + * according to their + * {@linkplain #insertProviderAt(java.security.Provider, int) + * preference order}. * *

      The selection criteria are represented by a map. * Each map entry represents a selection criterion. @@ -567,16 +569,18 @@ public final class Security { * criteria. The key for any entry in such a map must be in one of the * following two formats: *

        - *
      • <crypto_service>.<algorithm_or_type> + *
      • {@literal .} *

        The cryptographic service name must not contain any dots. *

        The value associated with the key must be an empty string. *

        A provider * satisfies this selection criterion iff the provider implements the * specified algorithm or type for the specified cryptographic service. - *

      • <crypto_service>.<algorithm_or_type> <attribute_name> + *
      • {@literal }. + * {@literal } *

        The cryptographic service name must not contain any dots. There - * must be one or more space charaters between the <algorithm_or_type> - * and the <attribute_name>. + * must be one or more space charaters between the + * {@literal } + * and the {@literal }. *

        The value associated with the key must be a non-empty string. * A provider satisfies this selection criterion iff the * provider implements the specified algorithm or type for the specified diff --git a/src/share/classes/java/security/cert/X509CRL.java b/src/share/classes/java/security/cert/X509CRL.java index 0909ac0a6e6e90bfaa03a6798958bb03b145446d..5ce84847fa56d57fd83cf5da015058d8cbfbb7f2 100644 --- a/src/share/classes/java/security/cert/X509CRL.java +++ b/src/share/classes/java/security/cert/X509CRL.java @@ -249,7 +249,8 @@ public abstract class X509CRL extends CRL implements X509Extension { * The ASN.1 definition for this is: *

              * version    Version OPTIONAL,
        -     *             -- if present, must be v2

        + * -- if present, must be v2 + * * Version ::= INTEGER { v1(0), v2(1), v3(2) } * -- v3 does not apply to CRLs but appears for consistency * -- with definition of Version for certs @@ -413,7 +414,8 @@ public abstract class X509CRL extends CRL implements X509Extension { * signature algorithm. An example is the string "SHA256withRSA". * The ASN.1 definition for this is: *

        -     * signatureAlgorithm   AlgorithmIdentifier

        + * signatureAlgorithm AlgorithmIdentifier + * * AlgorithmIdentifier ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, * parameters ANY DEFINED BY algorithm OPTIONAL } diff --git a/src/share/classes/java/security/cert/X509CRLEntry.java b/src/share/classes/java/security/cert/X509CRLEntry.java index f355f62bcb83d8676ebe8053c62c2730c109e62c..268fa81958701309c17d2c33b1a95dfe83fcce25 100644 --- a/src/share/classes/java/security/cert/X509CRLEntry.java +++ b/src/share/classes/java/security/cert/X509CRLEntry.java @@ -43,11 +43,11 @@ import sun.security.x509.X509CRLEntryImpl; * crlEntryExtensions Extensions OPTIONAL * -- if present, must be v2 * } OPTIONAL - *

        + * * CertificateSerialNumber ::= INTEGER - *

        + * * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension - *

        + * * Extension ::= SEQUENCE { * extnId OBJECT IDENTIFIER, * critical BOOLEAN DEFAULT FALSE, diff --git a/src/share/classes/java/security/cert/X509Certificate.java b/src/share/classes/java/security/cert/X509Certificate.java index 9094dfcc95d1f32ae1ad8d2d3c0ddf605bac3c95..0aba5da60c0205f240ba6ab3de4c2de8c28ef9c4 100644 --- a/src/share/classes/java/security/cert/X509Certificate.java +++ b/src/share/classes/java/security/cert/X509Certificate.java @@ -126,10 +126,12 @@ implements X509Extension { * is valid. It is defined in * ASN.1 as: *

        -     * validity             Validity

        + * validity Validity + * * Validity ::= SEQUENCE { * notBefore CertificateValidityDate, - * notAfter CertificateValidityDate }

        + * notAfter CertificateValidityDate } + * * CertificateValidityDate ::= CHOICE { * utcTime UTCTime, * generalTime GeneralizedTime } @@ -165,7 +167,8 @@ implements X509Extension { * certificate. * The ASN.1 definition for this is: *

        -     * version  [0] EXPLICIT Version DEFAULT v1

        + * version [0] EXPLICIT Version DEFAULT v1 + * * Version ::= INTEGER { v1(0), v2(1), v3(2) } *

        * @return the version number, i.e. 1, 2 or 3. @@ -180,7 +183,7 @@ implements X509Extension { * serial number identify a unique certificate). * The ASN.1 definition for this is: *
        -     * serialNumber     CertificateSerialNumber

        + * serialNumber CertificateSerialNumber * * CertificateSerialNumber ::= INTEGER *

        @@ -204,7 +207,7 @@ implements X509Extension { * X.500 distinguished name (DN). * The ASN.1 definition for this is: *
        -     * issuer    Name

        + * issuer Name * * Name ::= CHOICE { RDNSequence } * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName @@ -295,11 +298,12 @@ implements X509Extension { * the certificate. * The relevant ASN.1 definitions are: *

        -     * validity             Validity

        + * validity Validity * * Validity ::= SEQUENCE { * notBefore CertificateValidityDate, - * notAfter CertificateValidityDate }

        + * notAfter CertificateValidityDate } + * * CertificateValidityDate ::= CHOICE { * utcTime UTCTime, * generalTime GeneralizedTime } @@ -348,7 +352,8 @@ implements X509Extension { * signature algorithm. An example is the string "SHA256withRSA". * The ASN.1 definition for this is: *

        -     * signatureAlgorithm   AlgorithmIdentifier

        + * signatureAlgorithm AlgorithmIdentifier + * * AlgorithmIdentifier ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, * parameters ANY DEFINED BY algorithm OPTIONAL } @@ -410,7 +415,8 @@ implements X509Extension { * *

        The ASN.1 definition for this is: *

        -     * issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL

        + * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL + * * UniqueIdentifier ::= BIT STRING *

        * @@ -424,7 +430,8 @@ implements X509Extension { * *

        The ASN.1 definition for this is: *

        -     * subjectUniqueID  [2]  IMPLICIT UniqueIdentifier OPTIONAL

        + * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL + * * UniqueIdentifier ::= BIT STRING *

        * @@ -474,9 +481,9 @@ implements X509Extension { * indicated in the key usage extension field. The ASN.1 * definition for this is: *
        -     * ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId

        + * ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId * - * KeyPurposeId ::= OBJECT IDENTIFIER

        + * KeyPurposeId ::= OBJECT IDENTIFIER *

        * * Key purposes may be defined by any organization with a diff --git a/src/share/classes/java/time/format/DateTimeFormatter.java b/src/share/classes/java/time/format/DateTimeFormatter.java index 158c741dff4e1e73866aedadbd43575a1b7f2160..2552f96e5ad36f3e5abdb8343eaff2bcaa418e82 100644 --- a/src/share/classes/java/time/format/DateTimeFormatter.java +++ b/src/share/classes/java/time/format/DateTimeFormatter.java @@ -1304,6 +1304,7 @@ public final class DateTimeFormatter { * LocalTime time = parsed.query(LocalTime::from); * Period extraDays = parsed.query(DateTimeFormatter.parsedExcessDays()); *
        + * @return a query that provides access to the excess days that were parsed */ public static final TemporalQuery parsedExcessDays() { return PARSED_EXCESS_DAYS; @@ -1344,6 +1345,7 @@ public final class DateTimeFormatter { * // validate leap-second is correct and apply correct smoothing * } *
        + * @return a query that provides access to whether a leap-second was parsed */ public static final TemporalQuery parsedLeapSecond() { return PARSED_LEAP_SECOND; diff --git a/src/share/classes/java/util/concurrent/ArrayBlockingQueue.java b/src/share/classes/java/util/concurrent/ArrayBlockingQueue.java index 74f1e985523b41d02a3b59ca2a46216790a76b9f..fe91686627d88cf99231ee661585acf16fb3a888 100644 --- a/src/share/classes/java/util/concurrent/ArrayBlockingQueue.java +++ b/src/share/classes/java/util/concurrent/ArrayBlockingQueue.java @@ -34,8 +34,15 @@ */ package java.util.concurrent; -import java.util.concurrent.locks.*; -import java.util.*; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.AbstractQueue; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.lang.ref.WeakReference; +import java.util.Spliterators; +import java.util.Spliterator; /** * A bounded {@linkplain BlockingQueue blocking queue} backed by an @@ -102,19 +109,21 @@ public class ArrayBlockingQueue extends AbstractQueue /** Main lock guarding all access */ final ReentrantLock lock; + /** Condition for waiting takes */ private final Condition notEmpty; + /** Condition for waiting puts */ private final Condition notFull; - // Internal helper methods - /** - * Circularly increment i. + * Shared state for currently active iterators, or null if there + * are known not to be any. Allows queue operations to update + * iterator state. */ - final int inc(int i) { - return (++i == items.length) ? 0 : i; - } + transient Itrs itrs = null; + + // Internal helper methods /** * Circularly decrement i. @@ -123,11 +132,6 @@ public class ArrayBlockingQueue extends AbstractQueue return ((i == 0) ? items.length : i) - 1; } - @SuppressWarnings("unchecked") - static E cast(Object item) { - return (E) item; - } - /** * Returns item at index i. */ @@ -150,10 +154,14 @@ public class ArrayBlockingQueue extends AbstractQueue * Inserts element at current put position, advances, and signals. * Call only when holding lock. */ - private void insert(E x) { + private void enqueue(E x) { + // assert lock.getHoldCount() == 1; + // assert items[putIndex] == null; + final Object[] items = this.items; items[putIndex] = x; - putIndex = inc(putIndex); - ++count; + if (++putIndex == items.length) + putIndex = 0; + count++; notEmpty.signal(); } @@ -161,43 +169,62 @@ public class ArrayBlockingQueue extends AbstractQueue * Extracts element at current take position, advances, and signals. * Call only when holding lock. */ - private E extract() { + private E dequeue() { + // assert lock.getHoldCount() == 1; + // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; - takeIndex = inc(takeIndex); - --count; + if (++takeIndex == items.length) + takeIndex = 0; + count--; + if (itrs != null) + itrs.elementDequeued(); notFull.signal(); return x; } /** - * Deletes item at position i. - * Utility for remove and iterator.remove. + * Deletes item at array index removeIndex. + * Utility for remove(Object) and iterator.remove. * Call only when holding lock. */ - void removeAt(int i) { + void removeAt(final int removeIndex) { + // assert lock.getHoldCount() == 1; + // assert items[removeIndex] != null; + // assert removeIndex >= 0 && removeIndex < items.length; final Object[] items = this.items; - // if removing front item, just advance - if (i == takeIndex) { + if (removeIndex == takeIndex) { + // removing front item; just advance items[takeIndex] = null; - takeIndex = inc(takeIndex); + if (++takeIndex == items.length) + takeIndex = 0; + count--; + if (itrs != null) + itrs.elementDequeued(); } else { + // an "interior" remove + // slide over all others up through putIndex. - for (;;) { - int nexti = inc(i); - if (nexti != putIndex) { - items[i] = items[nexti]; - i = nexti; + final int putIndex = this.putIndex; + for (int i = removeIndex;;) { + int next = i + 1; + if (next == items.length) + next = 0; + if (next != putIndex) { + items[i] = items[next]; + i = next; } else { items[i] = null; - putIndex = i; + this.putIndex = i; break; } } + count--; + if (itrs != null) + itrs.removedAt(removeIndex); } - --count; notFull.signal(); } @@ -302,7 +329,7 @@ public class ArrayBlockingQueue extends AbstractQueue if (count == items.length) return false; else { - insert(e); + enqueue(e); return true; } } finally { @@ -324,7 +351,7 @@ public class ArrayBlockingQueue extends AbstractQueue try { while (count == items.length) notFull.await(); - insert(e); + enqueue(e); } finally { lock.unlock(); } @@ -351,7 +378,7 @@ public class ArrayBlockingQueue extends AbstractQueue return false; nanos = notFull.awaitNanos(nanos); } - insert(e); + enqueue(e); return true; } finally { lock.unlock(); @@ -362,7 +389,7 @@ public class ArrayBlockingQueue extends AbstractQueue final ReentrantLock lock = this.lock; lock.lock(); try { - return (count == 0) ? null : extract(); + return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } @@ -374,7 +401,7 @@ public class ArrayBlockingQueue extends AbstractQueue try { while (count == 0) notEmpty.await(); - return extract(); + return dequeue(); } finally { lock.unlock(); } @@ -390,7 +417,7 @@ public class ArrayBlockingQueue extends AbstractQueue return null; nanos = notEmpty.awaitNanos(nanos); } - return extract(); + return dequeue(); } finally { lock.unlock(); } @@ -400,7 +427,7 @@ public class ArrayBlockingQueue extends AbstractQueue final ReentrantLock lock = this.lock; lock.lock(); try { - return (count == 0) ? null : itemAt(takeIndex); + return itemAt(takeIndex); // null when queue is empty } finally { lock.unlock(); } @@ -469,11 +496,17 @@ public class ArrayBlockingQueue extends AbstractQueue final ReentrantLock lock = this.lock; lock.lock(); try { - for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) { - if (o.equals(items[i])) { - removeAt(i); - return true; - } + if (count > 0) { + final int putIndex = this.putIndex; + int i = takeIndex; + do { + if (o.equals(items[i])) { + removeAt(i); + return true; + } + if (++i == items.length) + i = 0; + } while (i != putIndex); } return false; } finally { @@ -495,9 +528,16 @@ public class ArrayBlockingQueue extends AbstractQueue final ReentrantLock lock = this.lock; lock.lock(); try { - for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) - if (o.equals(items[i])) - return true; + if (count > 0) { + final int putIndex = this.putIndex; + int i = takeIndex; + do { + if (o.equals(items[i])) + return true; + if (++i == items.length) + i = 0; + } while (i != putIndex); + } return false; } finally { lock.unlock(); @@ -518,18 +558,23 @@ public class ArrayBlockingQueue extends AbstractQueue * @return an array containing all of the elements in this queue */ public Object[] toArray() { - final Object[] items = this.items; + Object[] a; final ReentrantLock lock = this.lock; lock.lock(); try { final int count = this.count; - Object[] a = new Object[count]; - for (int i = takeIndex, k = 0; k < count; i = inc(i), k++) - a[k] = items[i]; - return a; + a = new Object[count]; + int n = items.length - takeIndex; + if (count <= n) + System.arraycopy(items, takeIndex, a, 0, count); + else { + System.arraycopy(items, takeIndex, a, 0, n); + System.arraycopy(items, 0, a, n, count - n); + } } finally { lock.unlock(); } + return a; } /** @@ -553,8 +598,7 @@ public class ArrayBlockingQueue extends AbstractQueue * The following code can be used to dump the queue into a newly * allocated array of {@code String}: * - *
        -     *     String[] y = x.toArray(new String[0]);
        + *
         {@code String[] y = x.toArray(new String[0]);}
        * * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. @@ -579,14 +623,19 @@ public class ArrayBlockingQueue extends AbstractQueue if (len < count) a = (T[])java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), count); - for (int i = takeIndex, k = 0; k < count; i = inc(i), k++) - a[k] = (T) items[i]; + int n = items.length - takeIndex; + if (count <= n) + System.arraycopy(items, takeIndex, a, 0, count); + else { + System.arraycopy(items, takeIndex, a, 0, n); + System.arraycopy(items, 0, a, n, count - n); + } if (len > count) a[count] = null; - return a; } finally { lock.unlock(); } + return a; } public String toString() { @@ -597,14 +646,17 @@ public class ArrayBlockingQueue extends AbstractQueue if (k == 0) return "[]"; + final Object[] items = this.items; StringBuilder sb = new StringBuilder(); sb.append('['); - for (int i = takeIndex; ; i = inc(i)) { + for (int i = takeIndex; ; ) { Object e = items[i]; sb.append(e == this ? "(this Collection)" : e); if (--k == 0) return sb.append(']').toString(); sb.append(',').append(' '); + if (++i == items.length) + i = 0; } } finally { lock.unlock(); @@ -620,12 +672,22 @@ public class ArrayBlockingQueue extends AbstractQueue final ReentrantLock lock = this.lock; lock.lock(); try { - for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) - items[i] = null; - count = 0; - putIndex = 0; - takeIndex = 0; - notFull.signalAll(); + int k = count; + if (k > 0) { + final int putIndex = this.putIndex; + int i = takeIndex; + do { + items[i] = null; + if (++i == items.length) + i = 0; + } while (i != putIndex); + takeIndex = putIndex; + count = 0; + if (itrs != null) + itrs.queueIsEmpty(); + for (; k > 0 && lock.hasWaiters(notFull); k--) + notFull.signal(); + } } finally { lock.unlock(); } @@ -638,34 +700,7 @@ public class ArrayBlockingQueue extends AbstractQueue * @throws IllegalArgumentException {@inheritDoc} */ public int drainTo(Collection c) { - checkNotNull(c); - if (c == this) - throw new IllegalArgumentException(); - final Object[] items = this.items; - final ReentrantLock lock = this.lock; - lock.lock(); - try { - int i = takeIndex; - int n = 0; - int max = count; - while (n < max) { - @SuppressWarnings("unchecked") - E x = (E) items[i]; - c.add(x); - items[i] = null; - i = inc(i); - ++n; - } - if (n > 0) { - count = 0; - putIndex = 0; - takeIndex = 0; - notFull.signalAll(); - } - return n; - } finally { - lock.unlock(); - } + return drainTo(c, Integer.MAX_VALUE); } /** @@ -684,23 +719,35 @@ public class ArrayBlockingQueue extends AbstractQueue final ReentrantLock lock = this.lock; lock.lock(); try { - int i = takeIndex; - int n = 0; - int max = (maxElements < count) ? maxElements : count; - while (n < max) { - @SuppressWarnings("unchecked") - E x = (E) items[i]; - c.add(x); - items[i] = null; - i = inc(i); - ++n; - } - if (n > 0) { - count -= n; - takeIndex = i; - notFull.signalAll(); + int n = Math.min(maxElements, count); + int take = takeIndex; + int i = 0; + try { + while (i < n) { + @SuppressWarnings("unchecked") + E x = (E) items[take]; + c.add(x); + items[take] = null; + if (++take == items.length) + take = 0; + i++; + } + return n; + } finally { + // Restore invariants even if c.add() threw + if (i > 0) { + count -= i; + takeIndex = take; + if (itrs != null) { + if (count == 0) + itrs.queueIsEmpty(); + else if (i > take) + itrs.takeIndexWrapped(); + } + for (; i > 0 && lock.hasWaiters(notFull); i--) + notFull.signal(); + } } - return n; } finally { lock.unlock(); } @@ -710,12 +757,12 @@ public class ArrayBlockingQueue extends AbstractQueue * Returns an iterator over the elements in this queue in proper sequence. * The elements will be returned in order from first (head) to last (tail). * - *

        The returned {@code Iterator} is a "weakly consistent" iterator that + *

        The returned iterator is a "weakly consistent" iterator that * will never throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + * ConcurrentModificationException}, and guarantees to traverse + * elements as they existed upon construction of the iterator, and + * may (but is not guaranteed to) reflect any modifications + * subsequent to construction. * * @return an iterator over the elements in this queue in proper sequence */ @@ -724,88 +771,634 @@ public class ArrayBlockingQueue extends AbstractQueue } /** - * Iterator for ArrayBlockingQueue. To maintain weak consistency - * with respect to puts and takes, we (1) read ahead one slot, so - * as to not report hasNext true but then not have an element to - * return -- however we later recheck this slot to use the most - * current value; (2) ensure that each array slot is traversed at - * most once (by tracking "remaining" elements); (3) skip over - * null slots, which can occur if takes race ahead of iterators. - * However, for circular array-based queues, we cannot rely on any - * well established definition of what it means to be weakly - * consistent with respect to interior removes since these may - * require slot overwrites in the process of sliding elements to - * cover gaps. So we settle for resiliency, operating on - * established apparent nexts, which may miss some elements that - * have moved between calls to next. + * Shared data between iterators and their queue, allowing queue + * modifications to update iterators when elements are removed. + * + * This adds a lot of complexity for the sake of correctly + * handling some uncommon operations, but the combination of + * circular-arrays and supporting interior removes (i.e., those + * not at head) would cause iterators to sometimes lose their + * places and/or (re)report elements they shouldn't. To avoid + * this, when a queue has one or more iterators, it keeps iterator + * state consistent by: + * + * (1) keeping track of the number of "cycles", that is, the + * number of times takeIndex has wrapped around to 0. + * (2) notifying all iterators via the callback removedAt whenever + * an interior element is removed (and thus other elements may + * be shifted). + * + * These suffice to eliminate iterator inconsistencies, but + * unfortunately add the secondary responsibility of maintaining + * the list of iterators. We track all active iterators in a + * simple linked list (accessed only when the queue's lock is + * held) of weak references to Itr. The list is cleaned up using + * 3 different mechanisms: + * + * (1) Whenever a new iterator is created, do some O(1) checking for + * stale list elements. + * + * (2) Whenever takeIndex wraps around to 0, check for iterators + * that have been unused for more than one wrap-around cycle. + * + * (3) Whenever the queue becomes empty, all iterators are notified + * and this entire data structure is discarded. + * + * So in addition to the removedAt callback that is necessary for + * correctness, iterators have the shutdown and takeIndexWrapped + * callbacks that help remove stale iterators from the list. + * + * Whenever a list element is examined, it is expunged if either + * the GC has determined that the iterator is discarded, or if the + * iterator reports that it is "detached" (does not need any + * further state updates). Overhead is maximal when takeIndex + * never advances, iterators are discarded before they are + * exhausted, and all removals are interior removes, in which case + * all stale iterators are discovered by the GC. But even in this + * case we don't increase the amortized complexity. + * + * Care must be taken to keep list sweeping methods from + * reentrantly invoking another such method, causing subtle + * corruption bugs. + */ + class Itrs { + + /** + * Node in a linked list of weak iterator references. + */ + private class Node extends WeakReference { + Node next; + + Node(Itr iterator, Node next) { + super(iterator); + this.next = next; + } + } + + /** Incremented whenever takeIndex wraps around to 0 */ + int cycles = 0; + + /** Linked list of weak iterator references */ + private Node head; + + /** Used to expunge stale iterators */ + private Node sweeper = null; + + private static final int SHORT_SWEEP_PROBES = 4; + private static final int LONG_SWEEP_PROBES = 16; + + Itrs(Itr initial) { + register(initial); + } + + /** + * Sweeps itrs, looking for and expunging stale iterators. + * If at least one was found, tries harder to find more. + * Called only from iterating thread. + * + * @param tryHarder whether to start in try-harder mode, because + * there is known to be at least one iterator to collect + */ + void doSomeSweeping(boolean tryHarder) { + // assert lock.getHoldCount() == 1; + // assert head != null; + int probes = tryHarder ? LONG_SWEEP_PROBES : SHORT_SWEEP_PROBES; + Node o, p; + final Node sweeper = this.sweeper; + boolean passedGo; // to limit search to one full sweep + + if (sweeper == null) { + o = null; + p = head; + passedGo = true; + } else { + o = sweeper; + p = o.next; + passedGo = false; + } + + for (; probes > 0; probes--) { + if (p == null) { + if (passedGo) + break; + o = null; + p = head; + passedGo = true; + } + final Itr it = p.get(); + final Node next = p.next; + if (it == null || it.isDetached()) { + // found a discarded/exhausted iterator + probes = LONG_SWEEP_PROBES; // "try harder" + // unlink p + p.clear(); + p.next = null; + if (o == null) { + head = next; + if (next == null) { + // We've run out of iterators to track; retire + itrs = null; + return; + } + } + else + o.next = next; + } else { + o = p; + } + p = next; + } + + this.sweeper = (p == null) ? null : o; + } + + /** + * Adds a new iterator to the linked list of tracked iterators. + */ + void register(Itr itr) { + // assert lock.getHoldCount() == 1; + head = new Node(itr, head); + } + + /** + * Called whenever takeIndex wraps around to 0. + * + * Notifies all iterators, and expunges any that are now stale. + */ + void takeIndexWrapped() { + // assert lock.getHoldCount() == 1; + cycles++; + for (Node o = null, p = head; p != null;) { + final Itr it = p.get(); + final Node next = p.next; + if (it == null || it.takeIndexWrapped()) { + // unlink p + // assert it == null || it.isDetached(); + p.clear(); + p.next = null; + if (o == null) + head = next; + else + o.next = next; + } else { + o = p; + } + p = next; + } + if (head == null) // no more iterators to track + itrs = null; + } + + /** + * Called whenever an interior remove (not at takeIndex) occured. + * + * Notifies all iterators, and expunges any that are now stale. + */ + void removedAt(int removedIndex) { + for (Node o = null, p = head; p != null;) { + final Itr it = p.get(); + final Node next = p.next; + if (it == null || it.removedAt(removedIndex)) { + // unlink p + // assert it == null || it.isDetached(); + p.clear(); + p.next = null; + if (o == null) + head = next; + else + o.next = next; + } else { + o = p; + } + p = next; + } + if (head == null) // no more iterators to track + itrs = null; + } + + /** + * Called whenever the queue becomes empty. + * + * Notifies all active iterators that the queue is empty, + * clears all weak refs, and unlinks the itrs datastructure. + */ + void queueIsEmpty() { + // assert lock.getHoldCount() == 1; + for (Node p = head; p != null; p = p.next) { + Itr it = p.get(); + if (it != null) { + p.clear(); + it.shutdown(); + } + } + head = null; + itrs = null; + } + + /** + * Called whenever an element has been dequeued (at takeIndex). + */ + void elementDequeued() { + // assert lock.getHoldCount() == 1; + if (count == 0) + queueIsEmpty(); + else if (takeIndex == 0) + takeIndexWrapped(); + } + } + + /** + * Iterator for ArrayBlockingQueue. + * + * To maintain weak consistency with respect to puts and takes, we + * read ahead one slot, so as to not report hasNext true but then + * not have an element to return. + * + * We switch into "detached" mode (allowing prompt unlinking from + * itrs without help from the GC) when all indices are negative, or + * when hasNext returns false for the first time. This allows the + * iterator to track concurrent updates completely accurately, + * except for the corner case of the user calling Iterator.remove() + * after hasNext() returned false. Even in this case, we ensure + * that we don't remove the wrong element by keeping track of the + * expected element to remove, in lastItem. Yes, we may fail to + * remove lastItem from the queue if it moved due to an interleaved + * interior remove while in detached mode. */ private class Itr implements Iterator { - private int remaining; // Number of elements yet to be returned - private int nextIndex; // Index of element to be returned by next - private E nextItem; // Element to be returned by next call to next - private E lastItem; // Element returned by last call to next - private int lastRet; // Index of last element returned, or -1 if none + /** Index to look for new nextItem; NONE at end */ + private int cursor; + + /** Element to be returned by next call to next(); null if none */ + private E nextItem; + + /** Index of nextItem; NONE if none, REMOVED if removed elsewhere */ + private int nextIndex; + + /** Last element returned; null if none or not detached. */ + private E lastItem; + + /** Index of lastItem, NONE if none, REMOVED if removed elsewhere */ + private int lastRet; + + /** Previous value of takeIndex, or DETACHED when detached */ + private int prevTakeIndex; + + /** Previous value of iters.cycles */ + private int prevCycles; + + /** Special index value indicating "not available" or "undefined" */ + private static final int NONE = -1; + + /** + * Special index value indicating "removed elsewhere", that is, + * removed by some operation other than a call to this.remove(). + */ + private static final int REMOVED = -2; + + /** Special value for prevTakeIndex indicating "detached mode" */ + private static final int DETACHED = -3; Itr() { + // assert lock.getHoldCount() == 0; + lastRet = NONE; final ReentrantLock lock = ArrayBlockingQueue.this.lock; lock.lock(); try { - lastRet = -1; - if ((remaining = count) > 0) + if (count == 0) { + // assert itrs == null; + cursor = NONE; + nextIndex = NONE; + prevTakeIndex = DETACHED; + } else { + final int takeIndex = ArrayBlockingQueue.this.takeIndex; + prevTakeIndex = takeIndex; nextItem = itemAt(nextIndex = takeIndex); + cursor = incCursor(takeIndex); + if (itrs == null) { + itrs = new Itrs(this); + } else { + itrs.register(this); // in this order + itrs.doSomeSweeping(false); + } + prevCycles = itrs.cycles; + // assert takeIndex >= 0; + // assert prevTakeIndex == takeIndex; + // assert nextIndex >= 0; + // assert nextItem != null; + } } finally { lock.unlock(); } } + boolean isDetached() { + // assert lock.getHoldCount() == 1; + return prevTakeIndex < 0; + } + + private int incCursor(int index) { + // assert lock.getHoldCount() == 1; + if (++index == items.length) + index = 0; + if (index == putIndex) + index = NONE; + return index; + } + + /** + * Returns true if index is invalidated by the given number of + * dequeues, starting from prevTakeIndex. + */ + private boolean invalidated(int index, int prevTakeIndex, + long dequeues, int length) { + if (index < 0) + return false; + int distance = index - prevTakeIndex; + if (distance < 0) + distance += length; + return dequeues > distance; + } + + /** + * Adjusts indices to incorporate all dequeues since the last + * operation on this iterator. Call only from iterating thread. + */ + private void incorporateDequeues() { + // assert lock.getHoldCount() == 1; + // assert itrs != null; + // assert !isDetached(); + // assert count > 0; + + final int cycles = itrs.cycles; + final int takeIndex = ArrayBlockingQueue.this.takeIndex; + final int prevCycles = this.prevCycles; + final int prevTakeIndex = this.prevTakeIndex; + + if (cycles != prevCycles || takeIndex != prevTakeIndex) { + final int len = items.length; + // how far takeIndex has advanced since the previous + // operation of this iterator + long dequeues = (cycles - prevCycles) * len + + (takeIndex - prevTakeIndex); + + // Check indices for invalidation + if (invalidated(lastRet, prevTakeIndex, dequeues, len)) + lastRet = REMOVED; + if (invalidated(nextIndex, prevTakeIndex, dequeues, len)) + nextIndex = REMOVED; + if (invalidated(cursor, prevTakeIndex, dequeues, len)) + cursor = takeIndex; + + if (cursor < 0 && nextIndex < 0 && lastRet < 0) + detach(); + else { + this.prevCycles = cycles; + this.prevTakeIndex = takeIndex; + } + } + } + + /** + * Called when itrs should stop tracking this iterator, either + * because there are no more indices to update (cursor < 0 && + * nextIndex < 0 && lastRet < 0) or as a special exception, when + * lastRet >= 0, because hasNext() is about to return false for the + * first time. Call only from iterating thread. + */ + private void detach() { + // Switch to detached mode + // assert lock.getHoldCount() == 1; + // assert cursor == NONE; + // assert nextIndex < 0; + // assert lastRet < 0 || nextItem == null; + // assert lastRet < 0 ^ lastItem != null; + if (prevTakeIndex >= 0) { + // assert itrs != null; + prevTakeIndex = DETACHED; + // try to unlink from itrs (but not too hard) + itrs.doSomeSweeping(true); + } + } + + /** + * For performance reasons, we would like not to acquire a lock in + * hasNext in the common case. To allow for this, we only access + * fields (i.e. nextItem) that are not modified by update operations + * triggered by queue modifications. + */ public boolean hasNext() { - return remaining > 0; + // assert lock.getHoldCount() == 0; + if (nextItem != null) + return true; + noNext(); + return false; + } + + private void noNext() { + final ReentrantLock lock = ArrayBlockingQueue.this.lock; + lock.lock(); + try { + // assert cursor == NONE; + // assert nextIndex == NONE; + if (!isDetached()) { + // assert lastRet >= 0; + incorporateDequeues(); // might update lastRet + if (lastRet >= 0) { + lastItem = itemAt(lastRet); + // assert lastItem != null; + detach(); + } + } + // assert isDetached(); + // assert lastRet < 0 ^ lastItem != null; + } finally { + lock.unlock(); + } } public E next() { + // assert lock.getHoldCount() == 0; + final E x = nextItem; + if (x == null) + throw new NoSuchElementException(); final ReentrantLock lock = ArrayBlockingQueue.this.lock; lock.lock(); try { - if (remaining <= 0) - throw new NoSuchElementException(); + if (!isDetached()) + incorporateDequeues(); + // assert nextIndex != NONE; + // assert lastItem == null; lastRet = nextIndex; - E x = itemAt(nextIndex); // check for fresher value - if (x == null) { - x = nextItem; // we are forced to report old value - lastItem = null; // but ensure remove fails + final int cursor = this.cursor; + if (cursor >= 0) { + nextItem = itemAt(nextIndex = cursor); + // assert nextItem != null; + this.cursor = incCursor(cursor); + } else { + nextIndex = NONE; + nextItem = null; } - else - lastItem = x; - while (--remaining > 0 && // skip over nulls - (nextItem = itemAt(nextIndex = inc(nextIndex))) == null) - ; - return x; } finally { lock.unlock(); } + return x; } public void remove() { + // assert lock.getHoldCount() == 0; final ReentrantLock lock = ArrayBlockingQueue.this.lock; lock.lock(); try { - int i = lastRet; - if (i == -1) + if (!isDetached()) + incorporateDequeues(); // might update lastRet or detach + final int lastRet = this.lastRet; + this.lastRet = NONE; + if (lastRet >= 0) { + if (!isDetached()) + removeAt(lastRet); + else { + final E lastItem = this.lastItem; + // assert lastItem != null; + this.lastItem = null; + if (itemAt(lastRet) == lastItem) + removeAt(lastRet); + } + } else if (lastRet == NONE) throw new IllegalStateException(); - lastRet = -1; - E x = lastItem; - lastItem = null; - // only remove if item still at index - if (x != null && x == items[i]) { - boolean removingHead = (i == takeIndex); - removeAt(i); - if (!removingHead) - nextIndex = dec(nextIndex); - } + // else lastRet == REMOVED and the last returned element was + // previously asynchronously removed via an operation other + // than this.remove(), so nothing to do. + + if (cursor < 0 && nextIndex < 0) + detach(); } finally { lock.unlock(); + // assert lastRet == NONE; + // assert lastItem == null; + } + } + + /** + * Called to notify the iterator that the queue is empty, or that it + * has fallen hopelessly behind, so that it should abandon any + * further iteration, except possibly to return one more element + * from next(), as promised by returning true from hasNext(). + */ + void shutdown() { + // assert lock.getHoldCount() == 1; + cursor = NONE; + if (nextIndex >= 0) + nextIndex = REMOVED; + if (lastRet >= 0) { + lastRet = REMOVED; + lastItem = null; + } + prevTakeIndex = DETACHED; + // Don't set nextItem to null because we must continue to be + // able to return it on next(). + // + // Caller will unlink from itrs when convenient. + } + + private int distance(int index, int prevTakeIndex, int length) { + int distance = index - prevTakeIndex; + if (distance < 0) + distance += length; + return distance; + } + + /** + * Called whenever an interior remove (not at takeIndex) occured. + * + * @return true if this iterator should be unlinked from itrs + */ + boolean removedAt(int removedIndex) { + // assert lock.getHoldCount() == 1; + if (isDetached()) + return true; + + final int cycles = itrs.cycles; + final int takeIndex = ArrayBlockingQueue.this.takeIndex; + final int prevCycles = this.prevCycles; + final int prevTakeIndex = this.prevTakeIndex; + final int len = items.length; + int cycleDiff = cycles - prevCycles; + if (removedIndex < takeIndex) + cycleDiff++; + final int removedDistance = + (cycleDiff * len) + (removedIndex - prevTakeIndex); + // assert removedDistance >= 0; + int cursor = this.cursor; + if (cursor >= 0) { + int x = distance(cursor, prevTakeIndex, len); + if (x == removedDistance) { + if (cursor == putIndex) + this.cursor = cursor = NONE; + } + else if (x > removedDistance) { + // assert cursor != prevTakeIndex; + this.cursor = cursor = dec(cursor); + } + } + int lastRet = this.lastRet; + if (lastRet >= 0) { + int x = distance(lastRet, prevTakeIndex, len); + if (x == removedDistance) + this.lastRet = lastRet = REMOVED; + else if (x > removedDistance) + this.lastRet = lastRet = dec(lastRet); } + int nextIndex = this.nextIndex; + if (nextIndex >= 0) { + int x = distance(nextIndex, prevTakeIndex, len); + if (x == removedDistance) + this.nextIndex = nextIndex = REMOVED; + else if (x > removedDistance) + this.nextIndex = nextIndex = dec(nextIndex); + } + else if (cursor < 0 && nextIndex < 0 && lastRet < 0) { + this.prevTakeIndex = DETACHED; + return true; + } + return false; + } + + /** + * Called whenever takeIndex wraps around to zero. + * + * @return true if this iterator should be unlinked from itrs + */ + boolean takeIndexWrapped() { + // assert lock.getHoldCount() == 1; + if (isDetached()) + return true; + if (itrs.cycles - prevCycles > 1) { + // All the elements that existed at the time of the last + // operation are gone, so abandon further iteration. + shutdown(); + return true; + } + return false; } + +// /** Uncomment for debugging. */ +// public String toString() { +// return ("cursor=" + cursor + " " + +// "nextIndex=" + nextIndex + " " + +// "lastRet=" + lastRet + " " + +// "nextItem=" + nextItem + " " + +// "lastItem=" + lastItem + " " + +// "prevCycles=" + prevCycles + " " + +// "prevTakeIndex=" + prevTakeIndex + " " + +// "size()=" + size() + " " + +// "remainingCapacity()=" + remainingCapacity()); +// } } + public Spliterator spliterator() { + return Spliterators.spliterator + (this, Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT); + } } diff --git a/src/share/classes/java/util/concurrent/BlockingDeque.java b/src/share/classes/java/util/concurrent/BlockingDeque.java index 7f37f7e66ea3ebe4d47f9edd1cbcad350b3622f0..d98586a95b5fc250e18f426ef5e2f37b1fe4959d 100644 --- a/src/share/classes/java/util/concurrent/BlockingDeque.java +++ b/src/share/classes/java/util/concurrent/BlockingDeque.java @@ -41,17 +41,18 @@ import java.util.*; * for the deque to become non-empty when retrieving an element, and wait for * space to become available in the deque when storing an element. * - *

        BlockingDeque methods come in four forms, with different ways + *

        {@code BlockingDeque} methods come in four forms, with different ways * of handling operations that cannot be satisfied immediately, but may be * satisfied at some point in the future: * one throws an exception, the second returns a special value (either - * null or false, depending on the operation), the third + * {@code null} or {@code false}, depending on the operation), the third * blocks the current thread indefinitely until the operation can succeed, * and the fourth blocks for only a given maximum time limit before giving * up. These methods are summarized in the following table: * *

        *

    Attributes Automatically Placed in a Provider Object
    NameValue
    {@code Provider.id name}{@code String.valueOf(provider.getName())}
    + * * * * @@ -116,20 +117,21 @@ import java.util.*; * *
    Summary of BlockingDeque methods
    First Element (Head)
    * - *

    Like any {@link BlockingQueue}, a BlockingDeque is thread safe, + *

    Like any {@link BlockingQueue}, a {@code BlockingDeque} is thread safe, * does not permit null elements, and may (or may not) be * capacity-constrained. * - *

    A BlockingDeque implementation may be used directly as a FIFO - * BlockingQueue. The methods inherited from the - * BlockingQueue interface are precisely equivalent to - * BlockingDeque methods as indicated in the following table: + *

    A {@code BlockingDeque} implementation may be used directly as a FIFO + * {@code BlockingQueue}. The methods inherited from the + * {@code BlockingQueue} interface are precisely equivalent to + * {@code BlockingDeque} methods as indicated in the following table: * *

    * + * * - * - * + * + * * * * @@ -208,7 +210,7 @@ public interface BlockingDeque extends BlockingQueue, Deque { /** * Inserts the specified element at the front of this deque if it is * possible to do so immediately without violating capacity restrictions, - * throwing an IllegalStateException if no space is currently + * throwing an {@code IllegalStateException} if no space is currently * available. When using a capacity-restricted deque, it is generally * preferable to use {@link #offerFirst(Object) offerFirst}. * @@ -223,7 +225,7 @@ public interface BlockingDeque extends BlockingQueue, Deque { /** * Inserts the specified element at the end of this deque if it is * possible to do so immediately without violating capacity restrictions, - * throwing an IllegalStateException if no space is currently + * throwing an {@code IllegalStateException} if no space is currently * available. When using a capacity-restricted deque, it is generally * preferable to use {@link #offerLast(Object) offerLast}. * @@ -238,7 +240,7 @@ public interface BlockingDeque extends BlockingQueue, Deque { /** * Inserts the specified element at the front of this deque if it is * possible to do so immediately without violating capacity restrictions, - * returning true upon success and false if no space is + * returning {@code true} upon success and {@code false} if no space is * currently available. * When using a capacity-restricted deque, this method is generally * preferable to the {@link #addFirst(Object) addFirst} method, which can @@ -254,7 +256,7 @@ public interface BlockingDeque extends BlockingQueue, Deque { /** * Inserts the specified element at the end of this deque if it is * possible to do so immediately without violating capacity restrictions, - * returning true upon success and false if no space is + * returning {@code true} upon success and {@code false} if no space is * currently available. * When using a capacity-restricted deque, this method is generally * preferable to the {@link #addLast(Object) addLast} method, which can @@ -302,10 +304,10 @@ public interface BlockingDeque extends BlockingQueue, Deque { * * @param e the element to add * @param timeout how long to wait before giving up, in units of - * unit - * @param unit a TimeUnit determining how to interpret the - * timeout parameter - * @return true if successful, or false if + * {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the + * {@code timeout} parameter + * @return {@code true} if successful, or {@code false} if * the specified waiting time elapses before space is available * @throws InterruptedException if interrupted while waiting * @throws ClassCastException if the class of the specified element @@ -324,10 +326,10 @@ public interface BlockingDeque extends BlockingQueue, Deque { * * @param e the element to add * @param timeout how long to wait before giving up, in units of - * unit - * @param unit a TimeUnit determining how to interpret the - * timeout parameter - * @return true if successful, or false if + * {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the + * {@code timeout} parameter + * @return {@code true} if successful, or {@code false} if * the specified waiting time elapses before space is available * @throws InterruptedException if interrupted while waiting * @throws ClassCastException if the class of the specified element @@ -363,10 +365,10 @@ public interface BlockingDeque extends BlockingQueue, Deque { * become available. * * @param timeout how long to wait before giving up, in units of - * unit - * @param unit a TimeUnit determining how to interpret the - * timeout parameter - * @return the head of this deque, or null if the specified + * {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the + * {@code timeout} parameter + * @return the head of this deque, or {@code null} if the specified * waiting time elapses before an element is available * @throws InterruptedException if interrupted while waiting */ @@ -379,10 +381,10 @@ public interface BlockingDeque extends BlockingQueue, Deque { * become available. * * @param timeout how long to wait before giving up, in units of - * unit - * @param unit a TimeUnit determining how to interpret the - * timeout parameter - * @return the tail of this deque, or null if the specified + * {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the + * {@code timeout} parameter + * @return the tail of this deque, or {@code null} if the specified * waiting time elapses before an element is available * @throws InterruptedException if interrupted while waiting */ @@ -392,13 +394,13 @@ public interface BlockingDeque extends BlockingQueue, Deque { /** * Removes the first occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. - * More formally, removes the first element e such that - * o.equals(e) (if such an element exists). - * Returns true if this deque contained the specified element + * More formally, removes the first element {@code e} such that + * {@code o.equals(e)} (if such an element exists). + * Returns {@code true} if this deque contained the specified element * (or equivalently, if this deque changed as a result of the call). * * @param o element to be removed from this deque, if present - * @return true if an element was removed as a result of this call + * @return {@code true} if an element was removed as a result of this call * @throws ClassCastException if the class of the specified element * is incompatible with this deque * (optional) @@ -410,13 +412,13 @@ public interface BlockingDeque extends BlockingQueue, Deque { /** * Removes the last occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. - * More formally, removes the last element e such that - * o.equals(e) (if such an element exists). - * Returns true if this deque contained the specified element + * More formally, removes the last element {@code e} such that + * {@code o.equals(e)} (if such an element exists). + * Returns {@code true} if this deque contained the specified element * (or equivalently, if this deque changed as a result of the call). * * @param o element to be removed from this deque, if present - * @return true if an element was removed as a result of this call + * @return {@code true} if an element was removed as a result of this call * @throws ClassCastException if the class of the specified element * is incompatible with this deque * (optional) @@ -431,8 +433,8 @@ public interface BlockingDeque extends BlockingQueue, Deque { * Inserts the specified element into the queue represented by this deque * (in other words, at the tail of this deque) if it is possible to do so * immediately without violating capacity restrictions, returning - * true upon success and throwing an - * IllegalStateException if no space is currently available. + * {@code true} upon success and throwing an + * {@code IllegalStateException} if no space is currently available. * When using a capacity-restricted deque, it is generally preferable to * use {@link #offer(Object) offer}. * @@ -452,7 +454,7 @@ public interface BlockingDeque extends BlockingQueue, Deque { * Inserts the specified element into the queue represented by this deque * (in other words, at the tail of this deque) if it is possible to do so * immediately without violating capacity restrictions, returning - * true upon success and false if no space is currently + * {@code true} upon success and {@code false} if no space is currently * available. When using a capacity-restricted deque, this method is * generally preferable to the {@link #add} method, which can fail to * insert an element only by throwing an exception. @@ -494,8 +496,8 @@ public interface BlockingDeque extends BlockingQueue, Deque { * {@link #offerLast(Object,long,TimeUnit) offerLast}. * * @param e the element to add - * @return true if the element was added to this deque, else - * false + * @return {@code true} if the element was added to this deque, else + * {@code false} * @throws InterruptedException {@inheritDoc} * @throws ClassCastException if the class of the specified element * prevents it from being added to this deque @@ -522,11 +524,11 @@ public interface BlockingDeque extends BlockingQueue, Deque { /** * Retrieves and removes the head of the queue represented by this deque * (in other words, the first element of this deque), or returns - * null if this deque is empty. + * {@code null} if this deque is empty. * *

    This method is equivalent to {@link #pollFirst()}. * - * @return the head of this deque, or null if this deque is empty + * @return the head of this deque, or {@code null} if this deque is empty */ E poll(); @@ -550,7 +552,7 @@ public interface BlockingDeque extends BlockingQueue, Deque { *

    This method is equivalent to * {@link #pollFirst(long,TimeUnit) pollFirst}. * - * @return the head of this deque, or null if the + * @return the head of this deque, or {@code null} if the * specified waiting time elapses before an element is available * @throws InterruptedException if interrupted while waiting */ @@ -573,27 +575,27 @@ public interface BlockingDeque extends BlockingQueue, Deque { /** * Retrieves, but does not remove, the head of the queue represented by * this deque (in other words, the first element of this deque), or - * returns null if this deque is empty. + * returns {@code null} if this deque is empty. * *

    This method is equivalent to {@link #peekFirst() peekFirst}. * - * @return the head of this deque, or null if this deque is empty + * @return the head of this deque, or {@code null} if this deque is empty */ E peek(); /** * Removes the first occurrence of the specified element from this deque. * If the deque does not contain the element, it is unchanged. - * More formally, removes the first element e such that - * o.equals(e) (if such an element exists). - * Returns true if this deque contained the specified element + * More formally, removes the first element {@code e} such that + * {@code o.equals(e)} (if such an element exists). + * Returns {@code true} if this deque contained the specified element * (or equivalently, if this deque changed as a result of the call). * *

    This method is equivalent to * {@link #removeFirstOccurrence(Object) removeFirstOccurrence}. * * @param o element to be removed from this deque, if present - * @return true if this deque changed as a result of the call + * @return {@code true} if this deque changed as a result of the call * @throws ClassCastException if the class of the specified element * is incompatible with this deque * (optional) @@ -603,12 +605,12 @@ public interface BlockingDeque extends BlockingQueue, Deque { boolean remove(Object o); /** - * Returns true if this deque contains the specified element. - * More formally, returns true if and only if this deque contains - * at least one element e such that o.equals(e). + * Returns {@code true} if this deque contains the specified element. + * More formally, returns {@code true} if and only if this deque contains + * at least one element {@code e} such that {@code o.equals(e)}. * * @param o object to be checked for containment in this deque - * @return true if this deque contains the specified element + * @return {@code true} if this deque contains the specified element * @throws ClassCastException if the class of the specified element * is incompatible with this deque * (optional) @@ -635,9 +637,10 @@ public interface BlockingDeque extends BlockingQueue, Deque { // *** Stack methods *** /** - * Pushes an element onto the stack represented by this deque. In other - * words, inserts the element at the front of this deque unless it would - * violate capacity restrictions. + * Pushes an element onto the stack represented by this deque (in other + * words, at the head of this deque) if it is possible to do so + * immediately without violating capacity restrictions, throwing an + * {@code IllegalStateException} if no space is currently available. * *

    This method is equivalent to {@link #addFirst(Object) addFirst}. * diff --git a/src/share/classes/java/util/concurrent/BlockingQueue.java b/src/share/classes/java/util/concurrent/BlockingQueue.java index bb5eb6033244b2e9334e206b672fe35fd7279dad..8eb3ce2c98486a3a1bda9291a488c4575d29e300 100644 --- a/src/share/classes/java/util/concurrent/BlockingQueue.java +++ b/src/share/classes/java/util/concurrent/BlockingQueue.java @@ -44,17 +44,18 @@ import java.util.Queue; * element, and wait for space to become available in the queue when * storing an element. * - *

    BlockingQueue methods come in four forms, with different ways + *

    {@code BlockingQueue} methods come in four forms, with different ways * of handling operations that cannot be satisfied immediately, but may be * satisfied at some point in the future: * one throws an exception, the second returns a special value (either - * null or false, depending on the operation), the third + * {@code null} or {@code false}, depending on the operation), the third * blocks the current thread indefinitely until the operation can succeed, * and the fourth blocks for only a given maximum time limit before giving * up. These methods are summarized in the following table: * *

    *

    Comparison of BlockingQueue and BlockingDeque methods
    BlockingQueue Method Equivalent BlockingDeque Method {@code BlockingQueue} Method Equivalent {@code BlockingDeque} Method
    Insert
    + * * * * @@ -85,37 +86,37 @@ import java.util.Queue; * *
    Summary of BlockingQueue methods
    Throws exception
    * - *

    A BlockingQueue does not accept null elements. - * Implementations throw NullPointerException on attempts - * to add, put or offer a null. A - * null is used as a sentinel value to indicate failure of - * poll operations. + *

    A {@code BlockingQueue} does not accept {@code null} elements. + * Implementations throw {@code NullPointerException} on attempts + * to {@code add}, {@code put} or {@code offer} a {@code null}. A + * {@code null} is used as a sentinel value to indicate failure of + * {@code poll} operations. * - *

    A BlockingQueue may be capacity bounded. At any given - * time it may have a remainingCapacity beyond which no - * additional elements can be put without blocking. - * A BlockingQueue without any intrinsic capacity constraints always - * reports a remaining capacity of Integer.MAX_VALUE. + *

    A {@code BlockingQueue} may be capacity bounded. At any given + * time it may have a {@code remainingCapacity} beyond which no + * additional elements can be {@code put} without blocking. + * A {@code BlockingQueue} without any intrinsic capacity constraints always + * reports a remaining capacity of {@code Integer.MAX_VALUE}. * - *

    BlockingQueue implementations are designed to be used + *

    {@code BlockingQueue} implementations are designed to be used * primarily for producer-consumer queues, but additionally support * the {@link java.util.Collection} interface. So, for example, it is * possible to remove an arbitrary element from a queue using - * remove(x). However, such operations are in general + * {@code remove(x)}. However, such operations are in general * not performed very efficiently, and are intended for only * occasional use, such as when a queued message is cancelled. * - *

    BlockingQueue implementations are thread-safe. All + *

    {@code BlockingQueue} implementations are thread-safe. All * queuing methods achieve their effects atomically using internal * locks or other forms of concurrency control. However, the - * bulk Collection operations addAll, - * containsAll, retainAll and removeAll are + * bulk Collection operations {@code addAll}, + * {@code containsAll}, {@code retainAll} and {@code removeAll} are * not necessarily performed atomically unless specified * otherwise in an implementation. So it is possible, for example, for - * addAll(c) to fail (throwing an exception) after adding - * only some of the elements in c. + * {@code addAll(c)} to fail (throwing an exception) after adding + * only some of the elements in {@code c}. * - *

    A BlockingQueue does not intrinsically support + *

    A {@code BlockingQueue} does not intrinsically support * any kind of "close" or "shutdown" operation to * indicate that no more items will be added. The needs and usage of * such features tend to be implementation-dependent. For example, a @@ -125,7 +126,7 @@ import java.util.Queue; * *

    * Usage example, based on a typical producer-consumer scenario. - * Note that a BlockingQueue can safely be used with multiple + * Note that a {@code BlockingQueue} can safely be used with multiple * producers and multiple consumers. *

     {@code
      * class Producer implements Runnable {
    @@ -181,13 +182,13 @@ public interface BlockingQueue extends Queue {
         /**
          * Inserts the specified element into this queue if it is possible to do
          * so immediately without violating capacity restrictions, returning
    -     * true upon success and throwing an
    -     * IllegalStateException if no space is currently available.
    +     * {@code true} upon success and throwing an
    +     * {@code IllegalStateException} if no space is currently available.
          * When using a capacity-restricted queue, it is generally preferable to
          * use {@link #offer(Object) offer}.
          *
          * @param e the element to add
    -     * @return true (as specified by {@link Collection#add})
    +     * @return {@code true} (as specified by {@link Collection#add})
          * @throws IllegalStateException if the element cannot be added at this
          *         time due to capacity restrictions
          * @throws ClassCastException if the class of the specified element
    @@ -201,14 +202,14 @@ public interface BlockingQueue extends Queue {
         /**
          * Inserts the specified element into this queue if it is possible to do
          * so immediately without violating capacity restrictions, returning
    -     * true upon success and false if no space is currently
    +     * {@code true} upon success and {@code false} if no space is currently
          * available.  When using a capacity-restricted queue, this method is
          * generally preferable to {@link #add}, which can fail to insert an
          * element only by throwing an exception.
          *
          * @param e the element to add
    -     * @return true if the element was added to this queue, else
    -     *         false
    +     * @return {@code true} if the element was added to this queue, else
    +     *         {@code false}
          * @throws ClassCastException if the class of the specified element
          *         prevents it from being added to this queue
          * @throws NullPointerException if the specified element is null
    @@ -237,10 +238,10 @@ public interface BlockingQueue extends Queue {
          *
          * @param e the element to add
          * @param timeout how long to wait before giving up, in units of
    -     *        unit
    -     * @param unit a TimeUnit determining how to interpret the
    -     *        timeout parameter
    -     * @return true if successful, or false if
    +     *        {@code unit}
    +     * @param unit a {@code TimeUnit} determining how to interpret the
    +     *        {@code timeout} parameter
    +     * @return {@code true} if successful, or {@code false} if
          *         the specified waiting time elapses before space is available
          * @throws InterruptedException if interrupted while waiting
          * @throws ClassCastException if the class of the specified element
    @@ -266,10 +267,10 @@ public interface BlockingQueue extends Queue {
          * specified wait time if necessary for an element to become available.
          *
          * @param timeout how long to wait before giving up, in units of
    -     *        unit
    -     * @param unit a TimeUnit determining how to interpret the
    -     *        timeout parameter
    -     * @return the head of this queue, or null if the
    +     *        {@code unit}
    +     * @param unit a {@code TimeUnit} determining how to interpret the
    +     *        {@code timeout} parameter
    +     * @return the head of this queue, or {@code null} if the
          *         specified waiting time elapses before an element is available
          * @throws InterruptedException if interrupted while waiting
          */
    @@ -279,11 +280,11 @@ public interface BlockingQueue extends Queue {
         /**
          * Returns the number of additional elements that this queue can ideally
          * (in the absence of memory or resource constraints) accept without
    -     * blocking, or Integer.MAX_VALUE if there is no intrinsic
    +     * blocking, or {@code Integer.MAX_VALUE} if there is no intrinsic
          * limit.
          *
          * 

    Note that you cannot always tell if an attempt to insert - * an element will succeed by inspecting remainingCapacity + * an element will succeed by inspecting {@code remainingCapacity} * because it may be the case that another thread is about to * insert or remove an element. * @@ -293,14 +294,14 @@ public interface BlockingQueue extends Queue { /** * Removes a single instance of the specified element from this queue, - * if it is present. More formally, removes an element e such - * that o.equals(e), if this queue contains one or more such + * if it is present. More formally, removes an element {@code e} such + * that {@code o.equals(e)}, if this queue contains one or more such * elements. - * Returns true if this queue contained the specified element + * Returns {@code true} if this queue contained the specified element * (or equivalently, if this queue changed as a result of the call). * * @param o element to be removed from this queue, if present - * @return true if this queue changed as a result of the call + * @return {@code true} if this queue changed as a result of the call * @throws ClassCastException if the class of the specified element * is incompatible with this queue * (optional) @@ -310,12 +311,12 @@ public interface BlockingQueue extends Queue { boolean remove(Object o); /** - * Returns true if this queue contains the specified element. - * More formally, returns true if and only if this queue contains - * at least one element e such that o.equals(e). + * Returns {@code true} if this queue contains the specified element. + * More formally, returns {@code true} if and only if this queue contains + * at least one element {@code e} such that {@code o.equals(e)}. * * @param o object to be checked for containment in this queue - * @return true if this queue contains the specified element + * @return {@code true} if this queue contains the specified element * @throws ClassCastException if the class of the specified element * is incompatible with this queue * (optional) @@ -329,10 +330,10 @@ public interface BlockingQueue extends Queue { * to the given collection. This operation may be more * efficient than repeatedly polling this queue. A failure * encountered while attempting to add elements to - * collection c may result in elements being in neither, + * collection {@code c} may result in elements being in neither, * either or both collections when the associated exception is * thrown. Attempts to drain a queue to itself result in - * IllegalArgumentException. Further, the behavior of + * {@code IllegalArgumentException}. Further, the behavior of * this operation is undefined if the specified collection is * modified while the operation is in progress. * @@ -353,10 +354,10 @@ public interface BlockingQueue extends Queue { * Removes at most the given number of available elements from * this queue and adds them to the given collection. A failure * encountered while attempting to add elements to - * collection c may result in elements being in neither, + * collection {@code c} may result in elements being in neither, * either or both collections when the associated exception is * thrown. Attempts to drain a queue to itself result in - * IllegalArgumentException. Further, the behavior of + * {@code IllegalArgumentException}. Further, the behavior of * this operation is undefined if the specified collection is * modified while the operation is in progress. * diff --git a/src/share/classes/java/util/concurrent/BrokenBarrierException.java b/src/share/classes/java/util/concurrent/BrokenBarrierException.java index 2c8f7e3396d6e693ede3e408731a5ac2793138fa..11f126e015ad47d495d09f2d0f53c5852a3c1b2d 100644 --- a/src/share/classes/java/util/concurrent/BrokenBarrierException.java +++ b/src/share/classes/java/util/concurrent/BrokenBarrierException.java @@ -49,13 +49,13 @@ public class BrokenBarrierException extends Exception { private static final long serialVersionUID = 7117394618823254244L; /** - * Constructs a BrokenBarrierException with no specified detail + * Constructs a {@code BrokenBarrierException} with no specified detail * message. */ public BrokenBarrierException() {} /** - * Constructs a BrokenBarrierException with the specified + * Constructs a {@code BrokenBarrierException} with the specified * detail message. * * @param message the detail message diff --git a/src/share/classes/java/util/concurrent/ConcurrentLinkedDeque.java b/src/share/classes/java/util/concurrent/ConcurrentLinkedDeque.java index 146934af475e1a09fb4d684bae045b89af625ead..6bb62f0f98c3fab7116ecc16e0046c43bb430b9c 100644 --- a/src/share/classes/java/util/concurrent/ConcurrentLinkedDeque.java +++ b/src/share/classes/java/util/concurrent/ConcurrentLinkedDeque.java @@ -42,6 +42,9 @@ import java.util.Deque; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; /** * An unbounded concurrent {@linkplain Deque deque} based on linked nodes. @@ -816,7 +819,7 @@ public class ConcurrentLinkedDeque * Creates an array list and fills it with elements of this list. * Used by toArray. * - * @return the arrayList + * @return the array list */ private ArrayList toArrayList() { ArrayList list = new ArrayList(); @@ -1024,11 +1027,27 @@ public class ConcurrentLinkedDeque } public E poll() { return pollFirst(); } - public E remove() { return removeFirst(); } public E peek() { return peekFirst(); } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E remove() { return removeFirst(); } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E pop() { return removeFirst(); } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ public E element() { return getFirst(); } + + /** + * @throws NullPointerException {@inheritDoc} + */ public void push(E e) { addFirst(e); } - public E pop() { return removeFirst(); } /** * Removes the first element {@code e} such that @@ -1385,6 +1404,99 @@ public class ConcurrentLinkedDeque Node nextNode(Node p) { return pred(p); } } + /** A customized variant of Spliterators.IteratorSpliterator */ + static final class CLDSpliterator implements Spliterator { + static final int MAX_BATCH = 1 << 25; // max batch array size; + final ConcurrentLinkedDeque queue; + Node current; // current node; null until initialized + int batch; // batch size for splits + boolean exhausted; // true when no more nodes + CLDSpliterator(ConcurrentLinkedDeque queue) { + this.queue = queue; + } + + public Spliterator trySplit() { + Node p; + final ConcurrentLinkedDeque q = this.queue; + int b = batch; + int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null)) { + if (p.item == null && p == (p = p.next)) + current = p = q.first(); + if (p != null && p.next != null) { + Object[] a = new Object[n]; + int i = 0; + do { + if ((a[i] = p.item) != null) + ++i; + if (p == (p = p.next)) + p = q.first(); + } while (p != null && i < n); + if ((current = p) == null) + exhausted = true; + if (i > 0) { + batch = i; + return Spliterators.spliterator + (a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT); + } + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + final ConcurrentLinkedDeque q = this.queue; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null)) { + exhausted = true; + do { + E e = p.item; + if (p == (p = p.next)) + p = q.first(); + if (e != null) + action.accept(e); + } while (p != null); + } + } + + public boolean tryAdvance(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + final ConcurrentLinkedDeque q = this.queue; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null)) { + E e; + do { + e = p.item; + if (p == (p = p.next)) + p = q.first(); + } while (e == null && p != null); + if ((current = p) == null) + exhausted = true; + if (e != null) { + action.accept(e); + return true; + } + } + return false; + } + + public long estimateSize() { return Long.MAX_VALUE; } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT; + } + } + + public Spliterator spliterator() { + return new CLDSpliterator(this); + } + /** * Saves this deque to a stream (that is, serializes it). * diff --git a/src/share/classes/java/util/concurrent/ConcurrentLinkedQueue.java b/src/share/classes/java/util/concurrent/ConcurrentLinkedQueue.java index abf12c597600fdd53d626b7704b6bcfc9055ace1..0b562bc217d9b079d75f0fc2af59b26136aceda5 100644 --- a/src/share/classes/java/util/concurrent/ConcurrentLinkedQueue.java +++ b/src/share/classes/java/util/concurrent/ConcurrentLinkedQueue.java @@ -41,6 +41,9 @@ import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; /** * An unbounded thread-safe {@linkplain Queue queue} based on linked nodes. @@ -56,7 +59,7 @@ import java.util.Queue; * Like most other concurrent collection implementations, this class * does not permit the use of {@code null} elements. * - *

    This implementation employs an efficient "wait-free" + *

    This implementation employs an efficient non-blocking * algorithm based on one described in Simple, * Fast, and Practical Non-Blocking and Blocking Concurrent Queue @@ -295,7 +298,7 @@ public class ConcurrentLinkedQueue extends AbstractQueue } /** - * Try to CAS head to p. If successful, repoint old head to itself + * Tries to CAS head to p. If successful, repoint old head to itself * as sentinel for succ(), below. */ final void updateHead(Node h, Node p) { @@ -792,6 +795,96 @@ public class ConcurrentLinkedQueue extends AbstractQueue tail = t; } + /** A customized variant of Spliterators.IteratorSpliterator */ + static final class CLQSpliterator implements Spliterator { + static final int MAX_BATCH = 1 << 25; // max batch array size; + final ConcurrentLinkedQueue queue; + Node current; // current node; null until initialized + int batch; // batch size for splits + boolean exhausted; // true when no more nodes + CLQSpliterator(ConcurrentLinkedQueue queue) { + this.queue = queue; + } + + public Spliterator trySplit() { + Node p; + final ConcurrentLinkedQueue q = this.queue; + int b = batch; + int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null) && + p.next != null) { + Object[] a = new Object[n]; + int i = 0; + do { + if ((a[i] = p.item) != null) + ++i; + if (p == (p = p.next)) + p = q.first(); + } while (p != null && i < n); + if ((current = p) == null) + exhausted = true; + if (i > 0) { + batch = i; + return Spliterators.spliterator + (a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT); + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + final ConcurrentLinkedQueue q = this.queue; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null)) { + exhausted = true; + do { + E e = p.item; + if (p == (p = p.next)) + p = q.first(); + if (e != null) + action.accept(e); + } while (p != null); + } + } + + public boolean tryAdvance(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + final ConcurrentLinkedQueue q = this.queue; + if (!exhausted && + ((p = current) != null || (p = q.first()) != null)) { + E e; + do { + e = p.item; + if (p == (p = p.next)) + p = q.first(); + } while (e == null && p != null); + if ((current = p) == null) + exhausted = true; + if (e != null) { + action.accept(e); + return true; + } + } + return false; + } + + public long estimateSize() { return Long.MAX_VALUE; } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT; + } + } + + public Spliterator spliterator() { + return new CLQSpliterator(this); + } + /** * Throws NullPointerException if argument is null. * diff --git a/src/share/classes/java/util/concurrent/ConcurrentSkipListMap.java b/src/share/classes/java/util/concurrent/ConcurrentSkipListMap.java index cd93e211890de75889e9740385200279d08523c3..99faf1668346d32b2e333d22dbf4181e0b633e9c 100644 --- a/src/share/classes/java/util/concurrent/ConcurrentSkipListMap.java +++ b/src/share/classes/java/util/concurrent/ConcurrentSkipListMap.java @@ -34,7 +34,25 @@ */ package java.util.concurrent; -import java.util.*; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; +import java.util.Spliterator; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.BiConsumer; +import java.util.function.Function; /** * A scalable concurrent {@link ConcurrentNavigableMap} implementation. @@ -45,38 +63,38 @@ import java.util.*; *

    This class implements a concurrent variant of SkipLists * providing expected average log(n) time cost for the - * containsKey, get, put and - * remove operations and their variants. Insertion, removal, + * {@code containsKey}, {@code get}, {@code put} and + * {@code remove} operations and their variants. Insertion, removal, * update, and access operations safely execute concurrently by * multiple threads. Iterators are weakly consistent, returning * elements reflecting the state of the map at some point at or since * the creation of the iterator. They do not throw {@link - * ConcurrentModificationException}, and may proceed concurrently with - * other operations. Ascending key ordered views and their iterators - * are faster than descending ones. + * java.util.ConcurrentModificationException ConcurrentModificationException}, + * and may proceed concurrently with other operations. Ascending key ordered + * views and their iterators are faster than descending ones. * - *

    All Map.Entry pairs returned by methods in this class + *

    All {@code Map.Entry} pairs returned by methods in this class * and its views represent snapshots of mappings at the time they were - * produced. They do not support the Entry.setValue + * produced. They do not support the {@code Entry.setValue} * method. (Note however that it is possible to change mappings in the - * associated map using put, putIfAbsent, or - * replace, depending on exactly which effect you need.) + * associated map using {@code put}, {@code putIfAbsent}, or + * {@code replace}, depending on exactly which effect you need.) * - *

    Beware that, unlike in most collections, the size + *

    Beware that, unlike in most collections, the {@code size} * method is not a constant-time operation. Because of the * asynchronous nature of these maps, determining the current number * of elements requires a traversal of the elements, and so may report * inaccurate results if this collection is modified during traversal. - * Additionally, the bulk operations putAll, equals, - * toArray, containsValue, and clear are + * Additionally, the bulk operations {@code putAll}, {@code equals}, + * {@code toArray}, {@code containsValue}, and {@code clear} are * not guaranteed to be performed atomically. For example, an - * iterator operating concurrently with a putAll operation + * iterator operating concurrently with a {@code putAll} operation * might view only some of the added elements. * *

    This class and its views and iterators implement all of the * optional methods of the {@link Map} and {@link Iterator} * interfaces. Like most other concurrent collections, this class does - * not permit the use of null keys or values because some + * not permit the use of {@code null} keys or values because some * null return values cannot be reliably distinguished from the absence of * elements. * @@ -89,7 +107,6 @@ import java.util.*; * @param the type of mapped values * @since 1.6 */ -@SuppressWarnings("unchecked") public class ConcurrentSkipListMap extends AbstractMap implements ConcurrentNavigableMap, Cloneable, @@ -257,7 +274,7 @@ public class ConcurrentSkipListMap extends AbstractMap * * Indexing uses skip list parameters that maintain good search * performance while using sparser-than-usual indices: The - * hardwired parameters k=1, p=0.5 (see method randomLevel) mean + * hardwired parameters k=1, p=0.5 (see method doPut) mean * that about one-quarter of the nodes have indices. Of those that * do, half have one level, a quarter have two, and so on (see * Pugh's Skip List Cookbook, sec 3.4). The expected total space @@ -295,6 +312,20 @@ public class ConcurrentSkipListMap extends AbstractMap * there is a fair amount of near-duplication of code to handle * variants. * + * To produce random values without interference across threads, + * we use within-JDK thread local random support (via the + * "secondary seed", to avoid interference with user-level + * ThreadLocalRandom.) + * + * A previous version of this class wrapped non-comparable keys + * with their comparators to emulate Comparables when using + * comparators vs Comparables. However, JVMs now appear to better + * handle infusing comparator-vs-comparable choice into search + * loops. Static method cpr(comparator, x, y) is used for all + * comparisons, which works well as long as the comparator + * argument is set up outside of loops (thus sometimes passed as + * an argument to internal methods) to avoid field re-reads. + * * For explanation of algorithms sharing at least a couple of * features with this one, see Mikhail Fomitchev's thesis * (http://www.cs.yorku.ca/~mikhail/), Keir Fraser's thesis @@ -322,12 +353,6 @@ public class ConcurrentSkipListMap extends AbstractMap private static final long serialVersionUID = -8627078645895051609L; - /** - * Generates the initial random seed for the cheaper per-instance - * random number generators used in randomLevel. - */ - private static final Random seedGenerator = new Random(); - /** * Special value used to identify base-level header */ @@ -339,17 +364,12 @@ public class ConcurrentSkipListMap extends AbstractMap private transient volatile HeadIndex head; /** - * The comparator used to maintain order in this map, or null - * if using natural ordering. + * The comparator used to maintain order in this map, or null if + * using natural ordering. (Non-private to simplify access in + * nested classes.) * @serial */ - private final Comparator comparator; - - /** - * Seed for simple random number generator. Not volatile since it - * doesn't matter too much if different threads don't see updates. - */ - private transient int randomSeed; + final Comparator comparator; /** Lazily initialized key set */ private transient KeySet keySet; @@ -365,12 +385,11 @@ public class ConcurrentSkipListMap extends AbstractMap * clear, readObject. and ConcurrentSkipListSet.clone. * (Note that comparator must be separately initialized.) */ - final void initialize() { + private void initialize() { keySet = null; entrySet = null; values = null; descendingMap = null; - randomSeed = seedGenerator.nextInt() | 0x0100; // ensure nonzero head = new HeadIndex(new Node(null, BASE_HEADER, null), null, null, 1); } @@ -438,7 +457,7 @@ public class ConcurrentSkipListMap extends AbstractMap * because callers will have already read value field and need * to use that read (not another done here) and so directly * test if value points to node. - * @param n a possibly null reference to a node + * * @return true if this node is a marker node */ boolean isMarker() { @@ -477,7 +496,7 @@ public class ConcurrentSkipListMap extends AbstractMap */ if (f == next && this == b.next) { if (f == null || f.value != f) // not already marked - appendMarker(f); + casNext(f, new Node(f)); else b.casNext(this, f.next); } @@ -487,13 +506,14 @@ public class ConcurrentSkipListMap extends AbstractMap * Returns value if this node contains a valid key-value pair, * else null. * @return this node's value if it isn't a marker or header or - * is deleted, else null. + * is deleted, else null */ V getValidValue() { Object v = value; if (v == this || v == BASE_HEADER) return null; - return (V)v; + @SuppressWarnings("unchecked") V vv = (V)v; + return vv; } /** @@ -502,10 +522,11 @@ public class ConcurrentSkipListMap extends AbstractMap * @return new entry or null */ AbstractMap.SimpleImmutableEntry createSnapshot() { - V v = getValidValue(); - if (v == null) + Object v = value; + if (v == null || v == this || v == BASE_HEADER) return null; - return new AbstractMap.SimpleImmutableEntry(key, v); + @SuppressWarnings("unchecked") V vv = (V)v; + return new AbstractMap.SimpleImmutableEntry(key, vv); } // UNSAFE mechanics @@ -588,7 +609,7 @@ public class ConcurrentSkipListMap extends AbstractMap * @return true if successful */ final boolean unlink(Index succ) { - return !indexesDeletedNode() && casRight(succ, succ.right); + return node.value != null && casRight(succ, succ.right); } // Unsafe mechanics @@ -622,80 +643,12 @@ public class ConcurrentSkipListMap extends AbstractMap /* ---------------- Comparison utilities -------------- */ /** - * Represents a key with a comparator as a Comparable. - * - * Because most sorted collections seem to use natural ordering on - * Comparables (Strings, Integers, etc), most internal methods are - * geared to use them. This is generally faster than checking - * per-comparison whether to use comparator or comparable because - * it doesn't require a (Comparable) cast for each comparison. - * (Optimizers can only sometimes remove such redundant checks - * themselves.) When Comparators are used, - * ComparableUsingComparators are created so that they act in the - * same way as natural orderings. This penalizes use of - * Comparators vs Comparables, which seems like the right - * tradeoff. - */ - static final class ComparableUsingComparator implements Comparable { - final K actualKey; - final Comparator cmp; - ComparableUsingComparator(K key, Comparator cmp) { - this.actualKey = key; - this.cmp = cmp; - } - public int compareTo(K k2) { - return cmp.compare(actualKey, k2); - } - } - - /** - * If using comparator, return a ComparableUsingComparator, else - * cast key as Comparable, which may cause ClassCastException, - * which is propagated back to caller. - */ - private Comparable comparable(Object key) - throws ClassCastException { - if (key == null) - throw new NullPointerException(); - if (comparator != null) - return new ComparableUsingComparator((K)key, comparator); - else - return (Comparable)key; - } - - /** - * Compares using comparator or natural ordering. Used when the - * ComparableUsingComparator approach doesn't apply. - */ - int compare(K k1, K k2) throws ClassCastException { - Comparator cmp = comparator; - if (cmp != null) - return cmp.compare(k1, k2); - else - return ((Comparable)k1).compareTo(k2); - } - - /** - * Returns true if given key greater than or equal to least and - * strictly less than fence, bypassing either test if least or - * fence are null. Needed mainly in submap operations. - */ - boolean inHalfOpenRange(K key, K least, K fence) { - if (key == null) - throw new NullPointerException(); - return ((least == null || compare(key, least) >= 0) && - (fence == null || compare(key, fence) < 0)); - } - - /** - * Returns true if given key greater than or equal to least and less - * or equal to fence. Needed mainly in submap operations. + * Compares using comparator or natural ordering if null. + * Called only by methods that have performed required type checks. */ - boolean inOpenRange(K key, K least, K fence) { - if (key == null) - throw new NullPointerException(); - return ((least == null || compare(key, least) >= 0) && - (fence == null || compare(key, fence) <= 0)); + @SuppressWarnings({"unchecked", "rawtypes"}) + static final int cpr(Comparator c, Object x, Object y) { + return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y); } /* ---------------- Traversal -------------- */ @@ -708,13 +661,11 @@ public class ConcurrentSkipListMap extends AbstractMap * @param key the key * @return a predecessor of key */ - private Node findPredecessor(Comparable key) { + private Node findPredecessor(Object key, Comparator cmp) { if (key == null) throw new NullPointerException(); // don't postpone errors for (;;) { - Index q = head; - Index r = q.right; - for (;;) { + for (Index q = head, r = q.right, d;;) { if (r != null) { Node n = r.node; K k = n.key; @@ -724,18 +675,16 @@ public class ConcurrentSkipListMap extends AbstractMap r = q.right; // reread r continue; } - if (key.compareTo(k) > 0) { + if (cpr(cmp, key, k) > 0) { q = r; r = r.right; continue; } } - Index d = q.down; - if (d != null) { - q = d; - r = d.right; - } else + if ((d = q.down) == null) return q.node; + q = d; + r = d.right; } } } @@ -784,54 +733,71 @@ public class ConcurrentSkipListMap extends AbstractMap * @param key the key * @return node holding key, or null if no such */ - private Node findNode(Comparable key) { - for (;;) { - Node b = findPredecessor(key); - Node n = b.next; - for (;;) { + private Node findNode(Object key) { + if (key == null) + throw new NullPointerException(); // don't postpone errors + Comparator cmp = comparator; + outer: for (;;) { + for (Node b = findPredecessor(key, cmp), n = b.next;;) { + Object v; int c; if (n == null) - return null; + break outer; Node f = n.next; if (n != b.next) // inconsistent read break; - Object v = n.value; - if (v == null) { // n is deleted + if ((v = n.value) == null) { // n is deleted n.helpDelete(b, f); break; } - if (v == n || b.value == null) // b is deleted + if (b.value == null || v == n) // b is deleted break; - int c = key.compareTo(n.key); - if (c == 0) + if ((c = cpr(cmp, key, n.key)) == 0) return n; if (c < 0) - return null; + break outer; b = n; n = f; } } + return null; } /** - * Gets value for key using findNode. - * @param okey the key + * Gets value for key. Almost the same as findNode, but returns + * the found value (to avoid retries during re-reads) + * + * @param key the key * @return the value, or null if absent */ - private V doGet(Object okey) { - Comparable key = comparable(okey); - /* - * Loop needed here and elsewhere in case value field goes - * null just as it is about to be returned, in which case we - * lost a race with a deletion, so must retry. - */ - for (;;) { - Node n = findNode(key); - if (n == null) - return null; - Object v = n.value; - if (v != null) - return (V)v; + private V doGet(Object key) { + if (key == null) + throw new NullPointerException(); + Comparator cmp = comparator; + outer: for (;;) { + for (Node b = findPredecessor(key, cmp), n = b.next;;) { + Object v; int c; + if (n == null) + break outer; + Node f = n.next; + if (n != b.next) // inconsistent read + break; + if ((v = n.value) == null) { // n is deleted + n.helpDelete(b, f); + break; + } + if (b.value == null || v == n) // b is deleted + break; + if ((c = cpr(cmp, key, n.key)) == 0) { + @SuppressWarnings("unchecked") V vv = (V)v; + return vv; + } + if (c < 0) + break outer; + b = n; + n = f; + } } + return null; } /* ---------------- Insertion -------------- */ @@ -839,187 +805,126 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Main insertion method. Adds element if not present, or * replaces value if present and onlyIfAbsent is false. - * @param kkey the key - * @param value the value that must be associated with key + * @param key the key + * @param value the value that must be associated with key * @param onlyIfAbsent if should not insert if already present * @return the old value, or null if newly inserted */ - private V doPut(K kkey, V value, boolean onlyIfAbsent) { - Comparable key = comparable(kkey); - for (;;) { - Node b = findPredecessor(key); - Node n = b.next; - for (;;) { + private V doPut(K key, V value, boolean onlyIfAbsent) { + Node z; // added node + if (key == null) + throw new NullPointerException(); + Comparator cmp = comparator; + outer: for (;;) { + for (Node b = findPredecessor(key, cmp), n = b.next;;) { if (n != null) { + Object v; int c; Node f = n.next; if (n != b.next) // inconsistent read break; - Object v = n.value; - if (v == null) { // n is deleted + if ((v = n.value) == null) { // n is deleted n.helpDelete(b, f); break; } - if (v == n || b.value == null) // b is deleted + if (b.value == null || v == n) // b is deleted break; - int c = key.compareTo(n.key); - if (c > 0) { + if ((c = cpr(cmp, key, n.key)) > 0) { b = n; n = f; continue; } if (c == 0) { - if (onlyIfAbsent || n.casValue(v, value)) - return (V)v; - else - break; // restart if lost race to replace value + if (onlyIfAbsent || n.casValue(v, value)) { + @SuppressWarnings("unchecked") V vv = (V)v; + return vv; + } + break; // restart if lost race to replace value } // else c < 0; fall through } - Node z = new Node(kkey, value, n); + z = new Node(key, value, n); if (!b.casNext(n, z)) break; // restart if lost race to append to b - int level = randomLevel(); - if (level > 0) - insertIndex(z, level); - return null; + break outer; } } - } - /** - * Returns a random level for inserting a new node. - * Hardwired to k=1, p=0.5, max 31 (see above and - * Pugh's "Skip List Cookbook", sec 3.4). - * - * This uses the simplest of the generators described in George - * Marsaglia's "Xorshift RNGs" paper. This is not a high-quality - * generator but is acceptable here. - */ - private int randomLevel() { - int x = randomSeed; - x ^= x << 13; - x ^= x >>> 17; - randomSeed = x ^= x << 5; - if ((x & 0x80000001) != 0) // test highest and lowest bits - return 0; - int level = 1; - while (((x >>>= 1) & 1) != 0) ++level; - return level; - } - - /** - * Creates and adds index nodes for the given node. - * @param z the node - * @param level the level of the index - */ - private void insertIndex(Node z, int level) { - HeadIndex h = head; - int max = h.level; - - if (level <= max) { - Index idx = null; - for (int i = 1; i <= level; ++i) - idx = new Index(z, idx, null); - addIndex(idx, h, level); - - } else { // Add a new level - /* - * To reduce interference by other threads checking for - * empty levels in tryReduceLevel, new levels are added - * with initialized right pointers. Which in turn requires - * keeping levels in an array to access them while - * creating new head index nodes from the opposite - * direction. - */ - level = max + 1; - Index[] idxs = (Index[])new Index[level+1]; + int rnd = ThreadLocalRandom.nextSecondarySeed(); + if ((rnd & 0x80000001) == 0) { // test highest and lowest bits + int level = 1, max; + while (((rnd >>>= 1) & 1) != 0) + ++level; Index idx = null; - for (int i = 1; i <= level; ++i) - idxs[i] = idx = new Index(z, idx, null); - - HeadIndex oldh; - int k; - for (;;) { - oldh = head; - int oldLevel = oldh.level; - if (level <= oldLevel) { // lost race to add level - k = level; - break; - } - HeadIndex newh = oldh; - Node oldbase = oldh.node; - for (int j = oldLevel+1; j <= level; ++j) - newh = new HeadIndex(oldbase, newh, idxs[j], j); - if (casHead(oldh, newh)) { - k = oldLevel; - break; - } + HeadIndex h = head; + if (level <= (max = h.level)) { + for (int i = 1; i <= level; ++i) + idx = new Index(z, idx, null); } - addIndex(idxs[k], oldh, k); - } - } - - /** - * Adds given index nodes from given level down to 1. - * @param idx the topmost index node being inserted - * @param h the value of head to use to insert. This must be - * snapshotted by callers to provide correct insertion level - * @param indexLevel the level of the index - */ - private void addIndex(Index idx, HeadIndex h, int indexLevel) { - // Track next level to insert in case of retries - int insertionLevel = indexLevel; - Comparable key = comparable(idx.node.key); - if (key == null) throw new NullPointerException(); - - // Similar to findPredecessor, but adding index nodes along - // path to key. - for (;;) { - int j = h.level; - Index q = h; - Index r = q.right; - Index t = idx; - for (;;) { - if (r != null) { - Node n = r.node; - // compare before deletion check avoids needing recheck - int c = key.compareTo(n.key); - if (n.value == null) { - if (!q.unlink(r)) - break; - r = q.right; - continue; - } - if (c > 0) { - q = r; - r = r.right; - continue; + else { // try to grow by one level + level = max + 1; // hold in array and later pick the one to use + @SuppressWarnings("unchecked")Index[] idxs = + (Index[])new Index[level+1]; + for (int i = 1; i <= level; ++i) + idxs[i] = idx = new Index(z, idx, null); + for (;;) { + h = head; + int oldLevel = h.level; + if (level <= oldLevel) // lost race to add level + break; + HeadIndex newh = h; + Node oldbase = h.node; + for (int j = oldLevel+1; j <= level; ++j) + newh = new HeadIndex(oldbase, newh, idxs[j], j); + if (casHead(h, newh)) { + h = newh; + idx = idxs[level = oldLevel]; + break; } } - - if (j == insertionLevel) { - // Don't insert index if node already deleted - if (t.indexesDeletedNode()) { - findNode(key); // cleans up - return; + } + // find insertion points and splice in + splice: for (int insertionLevel = level;;) { + int j = h.level; + for (Index q = h, r = q.right, t = idx;;) { + if (q == null || t == null) + break splice; + if (r != null) { + Node n = r.node; + // compare before deletion check avoids needing recheck + int c = cpr(cmp, key, n.key); + if (n.value == null) { + if (!q.unlink(r)) + break; + r = q.right; + continue; + } + if (c > 0) { + q = r; + r = r.right; + continue; + } } - if (!q.link(r, t)) - break; // restart - if (--insertionLevel == 0) { - // need final deletion check before return - if (t.indexesDeletedNode()) + + if (j == insertionLevel) { + if (!q.link(r, t)) + break; // restart + if (t.node.value == null) { findNode(key); - return; + break splice; + } + if (--insertionLevel == 0) + break splice; } - } - if (--j >= insertionLevel && j < indexLevel) - t = t.down; - q = q.down; - r = q.right; + if (--j >= insertionLevel && j < level) + t = t.down; + q = q.down; + r = q.right; + } } } + return null; } /* ---------------- Deletion -------------- */ @@ -1038,51 +943,52 @@ public class ConcurrentSkipListMap extends AbstractMap * search for it, and we'd like to ensure lack of garbage * retention, so must call to be sure. * - * @param okey the key + * @param key the key * @param value if non-null, the value that must be * associated with key * @return the node, or null if not found */ - final V doRemove(Object okey, Object value) { - Comparable key = comparable(okey); - for (;;) { - Node b = findPredecessor(key); - Node n = b.next; - for (;;) { + final V doRemove(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + Comparator cmp = comparator; + outer: for (;;) { + for (Node b = findPredecessor(key, cmp), n = b.next;;) { + Object v; int c; if (n == null) - return null; + break outer; Node f = n.next; if (n != b.next) // inconsistent read break; - Object v = n.value; - if (v == null) { // n is deleted + if ((v = n.value) == null) { // n is deleted n.helpDelete(b, f); break; } - if (v == n || b.value == null) // b is deleted + if (b.value == null || v == n) // b is deleted break; - int c = key.compareTo(n.key); - if (c < 0) - return null; + if ((c = cpr(cmp, key, n.key)) < 0) + break outer; if (c > 0) { b = n; n = f; continue; } if (value != null && !value.equals(v)) - return null; + break outer; if (!n.casValue(v, null)) break; if (!n.appendMarker(f) || !b.casNext(n, f)) - findNode(key); // Retry via findNode + findNode(key); // retry via findNode else { - findPredecessor(key); // Clean index + findPredecessor(key, cmp); // clean index if (head.right == null) tryReduceLevel(); } - return (V)v; + @SuppressWarnings("unchecked") V vv = (V)v; + return vv; } } + return null; } /** @@ -1126,11 +1032,9 @@ public class ConcurrentSkipListMap extends AbstractMap * Specialized variant of findNode to get first valid node. * @return first node or null if empty */ - Node findFirst() { - for (;;) { - Node b = head.node; - Node n = b.next; - if (n == null) + final Node findFirst() { + for (Node b, n;;) { + if ((n = (b = head.node).next) == null) return null; if (n.value != null) return n; @@ -1142,11 +1046,9 @@ public class ConcurrentSkipListMap extends AbstractMap * Removes first entry; returns its snapshot. * @return null if empty, else snapshot of first entry */ - Map.Entry doRemoveFirstEntry() { - for (;;) { - Node b = head.node; - Node n = b.next; - if (n == null) + private Map.Entry doRemoveFirstEntry() { + for (Node b, n;;) { + if ((n = (b = head.node).next) == null) return null; Node f = n.next; if (n != b.next) @@ -1161,7 +1063,8 @@ public class ConcurrentSkipListMap extends AbstractMap if (!n.appendMarker(f) || !b.casNext(n, f)) findFirst(); // retry clearIndexToFirst(); - return new AbstractMap.SimpleImmutableEntry(n.key, (V)v); + @SuppressWarnings("unchecked") V vv = (V)v; + return new AbstractMap.SimpleImmutableEntry(n.key, vv); } } @@ -1170,8 +1073,7 @@ public class ConcurrentSkipListMap extends AbstractMap */ private void clearIndexToFirst() { for (;;) { - Index q = head; - for (;;) { + for (Index q = head;;) { Index r = q.right; if (r != null && r.indexesDeletedNode() && !q.unlink(r)) break; @@ -1184,6 +1086,52 @@ public class ConcurrentSkipListMap extends AbstractMap } } + /** + * Removes last entry; returns its snapshot. + * Specialized variant of doRemove. + * @return null if empty, else snapshot of last entry + */ + private Map.Entry doRemoveLastEntry() { + for (;;) { + Node b = findPredecessorOfLast(); + Node n = b.next; + if (n == null) { + if (b.isBaseHeader()) // empty + return null; + else + continue; // all b's successors are deleted; retry + } + for (;;) { + Node f = n.next; + if (n != b.next) // inconsistent read + break; + Object v = n.value; + if (v == null) { // n is deleted + n.helpDelete(b, f); + break; + } + if (b.value == null || v == n) // b is deleted + break; + if (f != null) { + b = n; + n = f; + continue; + } + if (!n.casValue(v, null)) + break; + K key = n.key; + if (!n.appendMarker(f) || !b.casNext(n, f)) + findNode(key); // retry via findNode + else { // clean index + findPredecessor(key, comparator); + if (head.right == null) + tryReduceLevel(); + } + @SuppressWarnings("unchecked") V vv = (V)v; + return new AbstractMap.SimpleImmutableEntry(key, vv); + } + } + } /* ---------------- Finding and removing last element -------------- */ @@ -1191,7 +1139,7 @@ public class ConcurrentSkipListMap extends AbstractMap * Specialized version of find to get last valid node. * @return last node or null if empty */ - Node findLast() { + final Node findLast() { /* * findPredecessor can't be used to traverse index level * because this doesn't use comparisons. So traversals of @@ -1210,9 +1158,7 @@ public class ConcurrentSkipListMap extends AbstractMap } else if ((d = q.down) != null) { q = d; } else { - Node b = q.node; - Node n = b.next; - for (;;) { + for (Node b = q.node, n = b.next;;) { if (n == null) return b.isBaseHeader() ? null : b; Node f = n.next; // inconsistent read @@ -1223,7 +1169,7 @@ public class ConcurrentSkipListMap extends AbstractMap n.helpDelete(b, f); break; } - if (v == n || b.value == null) // b is deleted + if (b.value == null || v == n) // b is deleted break; b = n; n = f; @@ -1242,8 +1188,7 @@ public class ConcurrentSkipListMap extends AbstractMap */ private Node findPredecessorOfLast() { for (;;) { - Index q = head; - for (;;) { + for (Index q = head;;) { Index d, r; if ((r = q.right) != null) { if (r.indexesDeletedNode()) { @@ -1264,53 +1209,6 @@ public class ConcurrentSkipListMap extends AbstractMap } } - /** - * Removes last entry; returns its snapshot. - * Specialized variant of doRemove. - * @return null if empty, else snapshot of last entry - */ - Map.Entry doRemoveLastEntry() { - for (;;) { - Node b = findPredecessorOfLast(); - Node n = b.next; - if (n == null) { - if (b.isBaseHeader()) // empty - return null; - else - continue; // all b's successors are deleted; retry - } - for (;;) { - Node f = n.next; - if (n != b.next) // inconsistent read - break; - Object v = n.value; - if (v == null) { // n is deleted - n.helpDelete(b, f); - break; - } - if (v == n || b.value == null) // b is deleted - break; - if (f != null) { - b = n; - n = f; - continue; - } - if (!n.casValue(v, null)) - break; - K key = n.key; - Comparable ck = comparable(key); - if (!n.appendMarker(f) || !b.casNext(n, f)) - findNode(ck); // Retry via findNode - else { - findPredecessor(ck); // Clean index - if (head.right == null) - tryReduceLevel(); - } - return new AbstractMap.SimpleImmutableEntry(key, (V)v); - } - } - } - /* ---------------- Relational operations -------------- */ // Control values OR'ed as arguments to findNear @@ -1321,29 +1219,28 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Utility for ceiling, floor, lower, higher methods. - * @param kkey the key + * @param key the key * @param rel the relation -- OR'ed combination of EQ, LT, GT * @return nearest node fitting relation, or null if no such */ - Node findNear(K kkey, int rel) { - Comparable key = comparable(kkey); + final Node findNear(K key, int rel, Comparator cmp) { + if (key == null) + throw new NullPointerException(); for (;;) { - Node b = findPredecessor(key); - Node n = b.next; - for (;;) { + for (Node b = findPredecessor(key, cmp), n = b.next;;) { + Object v; if (n == null) return ((rel & LT) == 0 || b.isBaseHeader()) ? null : b; Node f = n.next; if (n != b.next) // inconsistent read break; - Object v = n.value; - if (v == null) { // n is deleted + if ((v = n.value) == null) { // n is deleted n.helpDelete(b, f); break; } - if (v == n || b.value == null) // b is deleted + if (b.value == null || v == n) // b is deleted break; - int c = key.compareTo(n.key); + int c = cpr(cmp, key, n.key); if ((c == 0 && (rel & EQ) != 0) || (c < 0 && (rel & LT) == 0)) return n; @@ -1361,9 +1258,10 @@ public class ConcurrentSkipListMap extends AbstractMap * @param rel the relation -- OR'ed combination of EQ, LT, GT * @return Entry fitting relation, or null if no such */ - AbstractMap.SimpleImmutableEntry getNear(K key, int rel) { + final AbstractMap.SimpleImmutableEntry getNear(K key, int rel) { + Comparator cmp = comparator; for (;;) { - Node n = findNear(key, rel); + Node n = findNear(key, rel, cmp); if (n == null) return null; AbstractMap.SimpleImmutableEntry e = n.createSnapshot(); @@ -1372,7 +1270,6 @@ public class ConcurrentSkipListMap extends AbstractMap } } - /* ---------------- Constructors -------------- */ /** @@ -1389,7 +1286,7 @@ public class ConcurrentSkipListMap extends AbstractMap * comparator. * * @param comparator the comparator that will be used to order this map. - * If null, the {@linkplain Comparable natural + * If {@code null}, the {@linkplain Comparable natural * ordering} of the keys will be used. */ public ConcurrentSkipListMap(Comparator comparator) { @@ -1403,7 +1300,7 @@ public class ConcurrentSkipListMap extends AbstractMap * the keys. * * @param m the map whose mappings are to be placed in this map - * @throws ClassCastException if the keys in m are not + * @throws ClassCastException if the keys in {@code m} are not * {@link Comparable}, or are not mutually comparable * @throws NullPointerException if the specified map or any of its keys * or values are null @@ -1430,7 +1327,7 @@ public class ConcurrentSkipListMap extends AbstractMap } /** - * Returns a shallow copy of this ConcurrentSkipListMap + * Returns a shallow copy of this {@code ConcurrentSkipListMap} * instance. (The keys and values themselves are not cloned.) * * @return a shallow copy of this map @@ -1477,8 +1374,14 @@ public class ConcurrentSkipListMap extends AbstractMap map.entrySet().iterator(); while (it.hasNext()) { Map.Entry e = it.next(); - int j = randomLevel(); - if (j > h.level) j = h.level + 1; + int rnd = ThreadLocalRandom.current().nextInt(); + int j = 0; + if ((rnd & 0x80000001) == 0) { + do { + ++j; + } while (((rnd >>>= 1) & 1) != 0); + if (j > h.level) j = h.level + 1; + } K k = e.getKey(); V v = e.getValue(); if (k == null || v == null) @@ -1511,7 +1414,7 @@ public class ConcurrentSkipListMap extends AbstractMap * * @serialData The key (Object) and value (Object) for each * key-value mapping represented by the map, followed by - * null. The key-value mappings are emitted in key-order + * {@code null}. The key-value mappings are emitted in key-order * (as determined by the Comparator, or by the keys' natural * ordering if no Comparator). */ @@ -1534,6 +1437,7 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Reconstitutes this map from a stream (that is, deserializes it). */ + @SuppressWarnings("unchecked") private void readObject(final java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in the Comparator and any hidden stuff @@ -1569,8 +1473,14 @@ public class ConcurrentSkipListMap extends AbstractMap throw new NullPointerException(); K key = (K) k; V val = (V) v; - int j = randomLevel(); - if (j > h.level) j = h.level + 1; + int rnd = ThreadLocalRandom.current().nextInt(); + int j = 0; + if ((rnd & 0x80000001) == 0) { + do { + ++j; + } while (((rnd >>>= 1) & 1) != 0); + if (j > h.level) j = h.level + 1; + } Node z = new Node(key, val, null); basepred.next = z; basepred = z; @@ -1595,11 +1505,11 @@ public class ConcurrentSkipListMap extends AbstractMap /* ------ Map API methods ------ */ /** - * Returns true if this map contains a mapping for the specified + * Returns {@code true} if this map contains a mapping for the specified * key. * * @param key key whose presence in this map is to be tested - * @return true if this map contains a mapping for the specified key + * @return {@code true} if this map contains a mapping for the specified key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key is null @@ -1626,6 +1536,22 @@ public class ConcurrentSkipListMap extends AbstractMap return doGet(key); } + /** + * Returns the value to which the specified key is mapped, + * or the given defaultValue if this map contains no mapping for the key. + * + * @param key the key + * @param defaultValue the value to return if this map contains + * no mapping for the given key + * @return the mapping for the key, if present; else the defaultValue + * @throws NullPointerException if the specified key is null + * @since 1.8 + */ + public V getOrDefault(Object key, V defaultValue) { + V v; + return (v = doGet(key)) == null ? defaultValue : v; + } + /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old @@ -1634,7 +1560,7 @@ public class ConcurrentSkipListMap extends AbstractMap * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or - * null if there was no mapping for the key + * {@code null} if there was no mapping for the key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key or value is null @@ -1650,7 +1576,7 @@ public class ConcurrentSkipListMap extends AbstractMap * * @param key key for which mapping should be removed * @return the previous value associated with the specified key, or - * null if there was no mapping for the key + * {@code null} if there was no mapping for the key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key is null @@ -1660,15 +1586,15 @@ public class ConcurrentSkipListMap extends AbstractMap } /** - * Returns true if this map maps one or more keys to the + * Returns {@code true} if this map maps one or more keys to the * specified value. This operation requires time linear in the * map size. Additionally, it is possible for the map to change * during execution of this method, in which case the returned * result may be inaccurate. * * @param value value whose presence in this map is to be tested - * @return true if a mapping to value exists; - * false otherwise + * @return {@code true} if a mapping to {@code value} exists; + * {@code false} otherwise * @throws NullPointerException if the specified value is null */ public boolean containsValue(Object value) { @@ -1684,8 +1610,8 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Returns the number of key-value mappings in this map. If this map - * contains more than Integer.MAX_VALUE elements, it - * returns Integer.MAX_VALUE. + * contains more than {@code Integer.MAX_VALUE} elements, it + * returns {@code Integer.MAX_VALUE}. * *

    Beware that, unlike in most collections, this method is * NOT a constant-time operation. Because of the @@ -1708,8 +1634,8 @@ public class ConcurrentSkipListMap extends AbstractMap } /** - * Returns true if this map contains no key-value mappings. - * @return true if this map contains no key-value mappings + * Returns {@code true} if this map contains no key-value mappings. + * @return {@code true} if this map contains no key-value mappings */ public boolean isEmpty() { return findFirst() == null; @@ -1722,6 +1648,140 @@ public class ConcurrentSkipListMap extends AbstractMap initialize(); } + /** + * If the specified key is not already associated with a value, + * attempts to compute its value using the given mapping function + * and enters it into this map unless {@code null}. The function + * is NOT guaranteed to be applied once atomically only + * if the value is not present. + * + * @param key key with which the specified value is to be associated + * @param mappingFunction the function to compute a value + * @return the current (existing or computed) value associated with + * the specified key, or null if the computed value is null + * @throws NullPointerException if the specified key is null + * or the mappingFunction is null + * @since 1.8 + */ + public V computeIfAbsent(K key, + Function mappingFunction) { + if (key == null || mappingFunction == null) + throw new NullPointerException(); + V v, p, r; + if ((v = doGet(key)) == null && + (r = mappingFunction.apply(key)) != null) + v = (p = doPut(key, r, true)) == null ? r : p; + return v; + } + + /** + * If the value for the specified key is present, attempts to + * compute a new mapping given the key and its current mapped + * value. The function is NOT guaranteed to be applied + * once atomically. + * + * @param key key with which a value may be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key is null + * or the remappingFunction is null + * @since 1.8 + */ + public V computeIfPresent(K key, + BiFunction remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + Node n; Object v; + while ((n = findNode(key)) != null) { + if ((v = n.value) != null) { + @SuppressWarnings("unchecked") V vv = (V) v; + V r = remappingFunction.apply(key, vv); + if (r != null) { + if (n.casValue(vv, r)) + return r; + } + else if (doRemove(key, vv) != null) + break; + } + } + return null; + } + + /** + * Attempts to compute a mapping for the specified key and its + * current mapped value (or {@code null} if there is no current + * mapping). The function is NOT guaranteed to be applied + * once atomically. + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key is null + * or the remappingFunction is null + * @since 1.8 + */ + public V compute(K key, + BiFunction remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + for (;;) { + Node n; Object v; V r; + if ((n = findNode(key)) == null) { + if ((r = remappingFunction.apply(key, null)) == null) + break; + if (doPut(key, r, true) == null) + return r; + } + else if ((v = n.value) != null) { + @SuppressWarnings("unchecked") V vv = (V) v; + if ((r = remappingFunction.apply(key, vv)) != null) { + if (n.casValue(vv, r)) + return r; + } + else if (doRemove(key, vv) != null) + break; + } + } + return null; + } + + /** + * If the specified key is not already associated with a value, + * associates it with the given value. Otherwise, replaces the + * value with the results of the given remapping function, or + * removes if {@code null}. The function is NOT + * guaranteed to be applied once atomically. + * + * @param key key with which the specified value is to be associated + * @param value the value to use if absent + * @param remappingFunction the function to recompute a value if present + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or value is null + * or the remappingFunction is null + * @since 1.8 + */ + public V merge(K key, V value, + BiFunction remappingFunction) { + if (key == null || value == null || remappingFunction == null) + throw new NullPointerException(); + for (;;) { + Node n; Object v; V r; + if ((n = findNode(key)) == null) { + if (doPut(key, value, true) == null) + return value; + } + else if ((v = n.value) != null) { + @SuppressWarnings("unchecked") V vv = (V) v; + if ((r = remappingFunction.apply(vv, value)) != null) { + if (n.casValue(vv, r)) + return r; + } + else if (doRemove(key, vv) != null) + return null; + } + } + } + /* ---------------- View methods -------------- */ /* @@ -1744,11 +1804,11 @@ public class ConcurrentSkipListMap extends AbstractMap * operations. It does not support the {@code add} or {@code addAll} * operations. * - *

    The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

    The view's {@code iterator} is a "weakly consistent" iterator that + * will never throw {@link java.util.ConcurrentModificationException + * ConcurrentModificationException}, and guarantees to traverse elements + * as they existed upon construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to construction. * *

    This method is equivalent to method {@code navigableKeySet}. * @@ -1771,16 +1831,16 @@ public class ConcurrentSkipListMap extends AbstractMap * The collection is backed by the map, so changes to the map are * reflected in the collection, and vice-versa. The collection * supports element removal, which removes the corresponding - * mapping from the map, via the Iterator.remove, - * Collection.remove, removeAll, - * retainAll and clear operations. It does not - * support the add or addAll operations. + * mapping from the map, via the {@code Iterator.remove}, + * {@code Collection.remove}, {@code removeAll}, + * {@code retainAll} and {@code clear} operations. It does not + * support the {@code add} or {@code addAll} operations. * - *

    The view's iterator is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

    The view's {@code iterator} is a "weakly consistent" iterator that + * will never throw {@link java.util.ConcurrentModificationException + * ConcurrentModificationException}, and guarantees to traverse elements + * as they existed upon construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to construction. */ public Collection values() { Values vs = values; @@ -1793,20 +1853,20 @@ public class ConcurrentSkipListMap extends AbstractMap * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. The set supports element * removal, which removes the corresponding mapping from the map, - * via the Iterator.remove, Set.remove, - * removeAll, retainAll and clear - * operations. It does not support the add or - * addAll operations. + * via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll} and {@code clear} + * operations. It does not support the {@code add} or + * {@code addAll} operations. * - *

    The view's iterator is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + *

    The view's {@code iterator} is a "weakly consistent" iterator that + * will never throw {@link java.util.ConcurrentModificationException + * ConcurrentModificationException}, and guarantees to traverse elements + * as they existed upon construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to construction. * - *

    The Map.Entry elements returned by - * iterator.next() do not support the - * setValue operation. + *

    The {@code Map.Entry} elements returned by + * {@code iterator.next()} do not support the + * {@code setValue} operation. * * @return a set view of the mappings contained in this map, * sorted in ascending key order @@ -1830,15 +1890,15 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Compares the specified object with this map for equality. - * Returns true if the given object is also a map and the + * Returns {@code true} if the given object is also a map and the * two maps represent the same mappings. More formally, two maps - * m1 and m2 represent the same mappings if - * m1.entrySet().equals(m2.entrySet()). This + * {@code m1} and {@code m2} represent the same mappings if + * {@code m1.entrySet().equals(m2.entrySet())}. This * operation may return misleading results if either map is * concurrently modified during execution of this method. * * @param o object to be compared for equality with this map - * @return true if the specified object is equal to this map + * @return {@code true} if the specified object is equal to this map */ public boolean equals(Object o) { if (o == this) @@ -1870,7 +1930,7 @@ public class ConcurrentSkipListMap extends AbstractMap * {@inheritDoc} * * @return the previous value associated with the specified key, - * or null if there was no mapping for the key + * or {@code null} if there was no mapping for the key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key or value is null @@ -1891,9 +1951,7 @@ public class ConcurrentSkipListMap extends AbstractMap public boolean remove(Object key, Object value) { if (key == null) throw new NullPointerException(); - if (value == null) - return false; - return doRemove(key, value) != null; + return value != null && doRemove(key, value) != null; } /** @@ -1904,15 +1962,13 @@ public class ConcurrentSkipListMap extends AbstractMap * @throws NullPointerException if any of the arguments are null */ public boolean replace(K key, V oldValue, V newValue) { - if (oldValue == null || newValue == null) + if (key == null || oldValue == null || newValue == null) throw new NullPointerException(); - Comparable k = comparable(key); for (;;) { - Node n = findNode(k); - if (n == null) + Node n; Object v; + if ((n = findNode(key)) == null) return false; - Object v = n.value; - if (v != null) { + if ((v = n.value) != null) { if (!oldValue.equals(v)) return false; if (n.casValue(v, newValue)) @@ -1925,22 +1981,22 @@ public class ConcurrentSkipListMap extends AbstractMap * {@inheritDoc} * * @return the previous value associated with the specified key, - * or null if there was no mapping for the key + * or {@code null} if there was no mapping for the key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key or value is null */ public V replace(K key, V value) { - if (value == null) + if (key == null || value == null) throw new NullPointerException(); - Comparable k = comparable(key); for (;;) { - Node n = findNode(k); - if (n == null) + Node n; Object v; + if ((n = findNode(key)) == null) return null; - Object v = n.value; - if (v != null && n.casValue(v, value)) - return (V)v; + if ((v = n.value) != null && n.casValue(v, value)) { + @SuppressWarnings("unchecked") V vv = (V)v; + return vv; + } } } @@ -2042,9 +2098,9 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Returns a key-value mapping associated with the greatest key - * strictly less than the given key, or null if there is + * strictly less than the given key, or {@code null} if there is * no such key. The returned entry does not support the - * Entry.setValue method. + * {@code Entry.setValue} method. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if the specified key is null @@ -2058,15 +2114,15 @@ public class ConcurrentSkipListMap extends AbstractMap * @throws NullPointerException if the specified key is null */ public K lowerKey(K key) { - Node n = findNear(key, LT); + Node n = findNear(key, LT, comparator); return (n == null) ? null : n.key; } /** * Returns a key-value mapping associated with the greatest key - * less than or equal to the given key, or null if there + * less than or equal to the given key, or {@code null} if there * is no such key. The returned entry does not support - * the Entry.setValue method. + * the {@code Entry.setValue} method. * * @param key the key * @throws ClassCastException {@inheritDoc} @@ -2082,15 +2138,15 @@ public class ConcurrentSkipListMap extends AbstractMap * @throws NullPointerException if the specified key is null */ public K floorKey(K key) { - Node n = findNear(key, LT|EQ); + Node n = findNear(key, LT|EQ, comparator); return (n == null) ? null : n.key; } /** * Returns a key-value mapping associated with the least key - * greater than or equal to the given key, or null if + * greater than or equal to the given key, or {@code null} if * there is no such entry. The returned entry does not - * support the Entry.setValue method. + * support the {@code Entry.setValue} method. * * @throws ClassCastException {@inheritDoc} * @throws NullPointerException if the specified key is null @@ -2104,15 +2160,15 @@ public class ConcurrentSkipListMap extends AbstractMap * @throws NullPointerException if the specified key is null */ public K ceilingKey(K key) { - Node n = findNear(key, GT|EQ); + Node n = findNear(key, GT|EQ, comparator); return (n == null) ? null : n.key; } /** * Returns a key-value mapping associated with the least key - * strictly greater than the given key, or null if there + * strictly greater than the given key, or {@code null} if there * is no such key. The returned entry does not support - * the Entry.setValue method. + * the {@code Entry.setValue} method. * * @param key the key * @throws ClassCastException {@inheritDoc} @@ -2128,15 +2184,15 @@ public class ConcurrentSkipListMap extends AbstractMap * @throws NullPointerException if the specified key is null */ public K higherKey(K key) { - Node n = findNear(key, GT); + Node n = findNear(key, GT, comparator); return (n == null) ? null : n.key; } /** * Returns a key-value mapping associated with the least - * key in this map, or null if the map is empty. + * key in this map, or {@code null} if the map is empty. * The returned entry does not support - * the Entry.setValue method. + * the {@code Entry.setValue} method. */ public Map.Entry firstEntry() { for (;;) { @@ -2151,9 +2207,9 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Returns a key-value mapping associated with the greatest - * key in this map, or null if the map is empty. + * key in this map, or {@code null} if the map is empty. * The returned entry does not support - * the Entry.setValue method. + * the {@code Entry.setValue} method. */ public Map.Entry lastEntry() { for (;;) { @@ -2168,9 +2224,9 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Removes and returns a key-value mapping associated with - * the least key in this map, or null if the map is empty. + * the least key in this map, or {@code null} if the map is empty. * The returned entry does not support - * the Entry.setValue method. + * the {@code Entry.setValue} method. */ public Map.Entry pollFirstEntry() { return doRemoveFirstEntry(); @@ -2178,9 +2234,9 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Removes and returns a key-value mapping associated with - * the greatest key in this map, or null if the map is empty. + * the greatest key in this map, or {@code null} if the map is empty. * The returned entry does not support - * the Entry.setValue method. + * the {@code Entry.setValue} method. */ public Map.Entry pollLastEntry() { return doRemoveLastEntry(); @@ -2202,13 +2258,11 @@ public class ConcurrentSkipListMap extends AbstractMap /** Initializes ascending iterator for entire range. */ Iter() { - for (;;) { - next = findFirst(); - if (next == null) - break; + while ((next = findFirst()) != null) { Object x = next.value; if (x != null && x != next) { - nextValue = (V) x; + @SuppressWarnings("unchecked") V vv = (V)x; + nextValue = vv; break; } } @@ -2223,13 +2277,11 @@ public class ConcurrentSkipListMap extends AbstractMap if (next == null) throw new NoSuchElementException(); lastReturned = next; - for (;;) { - next = next.next; - if (next == null) - break; + while ((next = next.next) != null) { Object x = next.value; if (x != null && x != next) { - nextValue = (V) x; + @SuppressWarnings("unchecked") V vv = (V)x; + nextValue = vv; break; } } @@ -2296,7 +2348,7 @@ public class ConcurrentSkipListMap extends AbstractMap static final List toList(Collection c) { // Using size() here would be a pessimization. - List list = new ArrayList(); + ArrayList list = new ArrayList(); for (E e : c) list.add(e); return list; @@ -2304,7 +2356,7 @@ public class ConcurrentSkipListMap extends AbstractMap static final class KeySet extends AbstractSet implements NavigableSet { - private final ConcurrentNavigableMap m; + final ConcurrentNavigableMap m; KeySet(ConcurrentNavigableMap map) { m = map; } public int size() { return m.size(); } public boolean isEmpty() { return m.isEmpty(); } @@ -2326,6 +2378,7 @@ public class ConcurrentSkipListMap extends AbstractMap Map.Entry e = m.pollLastEntry(); return (e == null) ? null : e.getKey(); } + @SuppressWarnings("unchecked") public Iterator iterator() { if (m instanceof ConcurrentSkipListMap) return ((ConcurrentSkipListMap)m).keyIterator(); @@ -2376,13 +2429,21 @@ public class ConcurrentSkipListMap extends AbstractMap public NavigableSet descendingSet() { return new KeySet(m.descendingMap()); } + @SuppressWarnings("unchecked") + public Spliterator spliterator() { + if (m instanceof ConcurrentSkipListMap) + return ((ConcurrentSkipListMap)m).keySpliterator(); + else + return (Spliterator)((SubMap)m).keyIterator(); + } } static final class Values extends AbstractCollection { - private final ConcurrentNavigableMap m; + final ConcurrentNavigableMap m; Values(ConcurrentNavigableMap map) { m = map; } + @SuppressWarnings("unchecked") public Iterator iterator() { if (m instanceof ConcurrentSkipListMap) return ((ConcurrentSkipListMap)m).valueIterator(); @@ -2403,14 +2464,21 @@ public class ConcurrentSkipListMap extends AbstractMap } public Object[] toArray() { return toList(this).toArray(); } public T[] toArray(T[] a) { return toList(this).toArray(a); } + @SuppressWarnings("unchecked") + public Spliterator spliterator() { + if (m instanceof ConcurrentSkipListMap) + return ((ConcurrentSkipListMap)m).valueSpliterator(); + else + return (Spliterator)((SubMap)m).valueIterator(); + } } static final class EntrySet extends AbstractSet> { - private final ConcurrentNavigableMap m; + final ConcurrentNavigableMap m; EntrySet(ConcurrentNavigableMap map) { m = map; } - + @SuppressWarnings("unchecked") public Iterator> iterator() { if (m instanceof ConcurrentSkipListMap) return ((ConcurrentSkipListMap)m).entryIterator(); @@ -2457,6 +2525,14 @@ public class ConcurrentSkipListMap extends AbstractMap } public Object[] toArray() { return toList(this).toArray(); } public T[] toArray(T[] a) { return toList(this).toArray(a); } + @SuppressWarnings("unchecked") + public Spliterator> spliterator() { + if (m instanceof ConcurrentSkipListMap) + return ((ConcurrentSkipListMap)m).entrySpliterator(); + else + return (Spliterator>) + ((SubMap)m).entryIterator(); + } } /** @@ -2466,8 +2542,8 @@ public class ConcurrentSkipListMap extends AbstractMap * underlying maps, differing in that mappings outside their range are * ignored, and attempts to add mappings outside their ranges result * in {@link IllegalArgumentException}. Instances of this class are - * constructed only using the subMap, headMap, and - * tailMap methods of their underlying maps. + * constructed only using the {@code subMap}, {@code headMap}, and + * {@code tailMap} methods of their underlying maps. * * @serial include */ @@ -2495,14 +2571,15 @@ public class ConcurrentSkipListMap extends AbstractMap private transient Collection valuesView; /** - * Creates a new submap, initializing all fields + * Creates a new submap, initializing all fields. */ SubMap(ConcurrentSkipListMap map, K fromKey, boolean fromInclusive, K toKey, boolean toInclusive, boolean isDescending) { + Comparator cmp = map.comparator; if (fromKey != null && toKey != null && - map.compare(fromKey, toKey) > 0) + cpr(cmp, fromKey, toKey) > 0) throw new IllegalArgumentException("inconsistent range"); this.m = map; this.lo = fromKey; @@ -2514,39 +2591,34 @@ public class ConcurrentSkipListMap extends AbstractMap /* ---------------- Utilities -------------- */ - private boolean tooLow(K key) { - if (lo != null) { - int c = m.compare(key, lo); - if (c < 0 || (c == 0 && !loInclusive)) - return true; - } - return false; + boolean tooLow(Object key, Comparator cmp) { + int c; + return (lo != null && ((c = cpr(cmp, key, lo)) < 0 || + (c == 0 && !loInclusive))); } - private boolean tooHigh(K key) { - if (hi != null) { - int c = m.compare(key, hi); - if (c > 0 || (c == 0 && !hiInclusive)) - return true; - } - return false; + boolean tooHigh(Object key, Comparator cmp) { + int c; + return (hi != null && ((c = cpr(cmp, key, hi)) > 0 || + (c == 0 && !hiInclusive))); } - private boolean inBounds(K key) { - return !tooLow(key) && !tooHigh(key); + boolean inBounds(Object key, Comparator cmp) { + return !tooLow(key, cmp) && !tooHigh(key, cmp); } - private void checkKeyBounds(K key) throws IllegalArgumentException { + void checkKeyBounds(K key, Comparator cmp) { if (key == null) throw new NullPointerException(); - if (!inBounds(key)) + if (!inBounds(key, cmp)) throw new IllegalArgumentException("key out of range"); } /** - * Returns true if node key is less than upper bound of range + * Returns true if node key is less than upper bound of range. */ - private boolean isBeforeEnd(ConcurrentSkipListMap.Node n) { + boolean isBeforeEnd(ConcurrentSkipListMap.Node n, + Comparator cmp) { if (n == null) return false; if (hi == null) @@ -2554,7 +2626,7 @@ public class ConcurrentSkipListMap extends AbstractMap K k = n.key; if (k == null) // pass by markers and headers return true; - int c = m.compare(k, hi); + int c = cpr(cmp, k, hi); if (c > 0 || (c == 0 && !hiInclusive)) return false; return true; @@ -2562,58 +2634,61 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Returns lowest node. This node might not be in range, so - * most usages need to check bounds + * most usages need to check bounds. */ - private ConcurrentSkipListMap.Node loNode() { + ConcurrentSkipListMap.Node loNode(Comparator cmp) { if (lo == null) return m.findFirst(); else if (loInclusive) - return m.findNear(lo, GT|EQ); + return m.findNear(lo, GT|EQ, cmp); else - return m.findNear(lo, GT); + return m.findNear(lo, GT, cmp); } /** * Returns highest node. This node might not be in range, so - * most usages need to check bounds + * most usages need to check bounds. */ - private ConcurrentSkipListMap.Node hiNode() { + ConcurrentSkipListMap.Node hiNode(Comparator cmp) { if (hi == null) return m.findLast(); else if (hiInclusive) - return m.findNear(hi, LT|EQ); + return m.findNear(hi, LT|EQ, cmp); else - return m.findNear(hi, LT); + return m.findNear(hi, LT, cmp); } /** - * Returns lowest absolute key (ignoring directonality) + * Returns lowest absolute key (ignoring directonality). */ - private K lowestKey() { - ConcurrentSkipListMap.Node n = loNode(); - if (isBeforeEnd(n)) + K lowestKey() { + Comparator cmp = m.comparator; + ConcurrentSkipListMap.Node n = loNode(cmp); + if (isBeforeEnd(n, cmp)) return n.key; else throw new NoSuchElementException(); } /** - * Returns highest absolute key (ignoring directonality) + * Returns highest absolute key (ignoring directonality). */ - private K highestKey() { - ConcurrentSkipListMap.Node n = hiNode(); + K highestKey() { + Comparator cmp = m.comparator; + ConcurrentSkipListMap.Node n = hiNode(cmp); if (n != null) { K last = n.key; - if (inBounds(last)) + if (inBounds(last, cmp)) return last; } throw new NoSuchElementException(); } - private Map.Entry lowestEntry() { + Map.Entry lowestEntry() { + Comparator cmp = m.comparator; for (;;) { - ConcurrentSkipListMap.Node n = loNode(); - if (!isBeforeEnd(n)) + ConcurrentSkipListMap.Node n = loNode(cmp); + if (!isBeforeEnd(n, cmp)) return null; Map.Entry e = n.createSnapshot(); if (e != null) @@ -2621,10 +2696,11 @@ public class ConcurrentSkipListMap extends AbstractMap } } - private Map.Entry highestEntry() { + Map.Entry highestEntry() { + Comparator cmp = m.comparator; for (;;) { - ConcurrentSkipListMap.Node n = hiNode(); - if (n == null || !inBounds(n.key)) + ConcurrentSkipListMap.Node n = hiNode(cmp); + if (n == null || !inBounds(n.key, cmp)) return null; Map.Entry e = n.createSnapshot(); if (e != null) @@ -2632,13 +2708,14 @@ public class ConcurrentSkipListMap extends AbstractMap } } - private Map.Entry removeLowest() { + Map.Entry removeLowest() { + Comparator cmp = m.comparator; for (;;) { - Node n = loNode(); + Node n = loNode(cmp); if (n == null) return null; K k = n.key; - if (!inBounds(k)) + if (!inBounds(k, cmp)) return null; V v = m.doRemove(k, null); if (v != null) @@ -2646,13 +2723,14 @@ public class ConcurrentSkipListMap extends AbstractMap } } - private Map.Entry removeHighest() { + Map.Entry removeHighest() { + Comparator cmp = m.comparator; for (;;) { - Node n = hiNode(); + Node n = hiNode(cmp); if (n == null) return null; K k = n.key; - if (!inBounds(k)) + if (!inBounds(k, cmp)) return null; V v = m.doRemove(k, null); if (v != null) @@ -2663,20 +2741,21 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Submap version of ConcurrentSkipListMap.getNearEntry */ - private Map.Entry getNearEntry(K key, int rel) { + Map.Entry getNearEntry(K key, int rel) { + Comparator cmp = m.comparator; if (isDescending) { // adjust relation for direction if ((rel & LT) == 0) rel |= LT; else rel &= ~LT; } - if (tooLow(key)) + if (tooLow(key, cmp)) return ((rel & LT) != 0) ? null : lowestEntry(); - if (tooHigh(key)) + if (tooHigh(key, cmp)) return ((rel & LT) != 0) ? highestEntry() : null; for (;;) { - Node n = m.findNear(key, rel); - if (n == null || !inBounds(n.key)) + Node n = m.findNear(key, rel, cmp); + if (n == null || !inBounds(n.key, cmp)) return null; K k = n.key; V v = n.getValidValue(); @@ -2686,35 +2765,36 @@ public class ConcurrentSkipListMap extends AbstractMap } // Almost the same as getNearEntry, except for keys - private K getNearKey(K key, int rel) { + K getNearKey(K key, int rel) { + Comparator cmp = m.comparator; if (isDescending) { // adjust relation for direction if ((rel & LT) == 0) rel |= LT; else rel &= ~LT; } - if (tooLow(key)) { + if (tooLow(key, cmp)) { if ((rel & LT) == 0) { - ConcurrentSkipListMap.Node n = loNode(); - if (isBeforeEnd(n)) + ConcurrentSkipListMap.Node n = loNode(cmp); + if (isBeforeEnd(n, cmp)) return n.key; } return null; } - if (tooHigh(key)) { + if (tooHigh(key, cmp)) { if ((rel & LT) != 0) { - ConcurrentSkipListMap.Node n = hiNode(); + ConcurrentSkipListMap.Node n = hiNode(cmp); if (n != null) { K last = n.key; - if (inBounds(last)) + if (inBounds(last, cmp)) return last; } } return null; } for (;;) { - Node n = m.findNear(key, rel); - if (n == null || !inBounds(n.key)) + Node n = m.findNear(key, rel, cmp); + if (n == null || !inBounds(n.key, cmp)) return null; K k = n.key; V v = n.getValidValue(); @@ -2727,30 +2807,28 @@ public class ConcurrentSkipListMap extends AbstractMap public boolean containsKey(Object key) { if (key == null) throw new NullPointerException(); - K k = (K)key; - return inBounds(k) && m.containsKey(k); + return inBounds(key, m.comparator) && m.containsKey(key); } public V get(Object key) { if (key == null) throw new NullPointerException(); - K k = (K)key; - return (!inBounds(k)) ? null : m.get(k); + return (!inBounds(key, m.comparator)) ? null : m.get(key); } public V put(K key, V value) { - checkKeyBounds(key); + checkKeyBounds(key, m.comparator); return m.put(key, value); } public V remove(Object key) { - K k = (K)key; - return (!inBounds(k)) ? null : m.remove(k); + return (!inBounds(key, m.comparator)) ? null : m.remove(key); } public int size() { + Comparator cmp = m.comparator; long count = 0; - for (ConcurrentSkipListMap.Node n = loNode(); - isBeforeEnd(n); + for (ConcurrentSkipListMap.Node n = loNode(cmp); + isBeforeEnd(n, cmp); n = n.next) { if (n.getValidValue() != null) ++count; @@ -2759,14 +2837,16 @@ public class ConcurrentSkipListMap extends AbstractMap } public boolean isEmpty() { - return !isBeforeEnd(loNode()); + Comparator cmp = m.comparator; + return !isBeforeEnd(loNode(cmp), cmp); } public boolean containsValue(Object value) { if (value == null) throw new NullPointerException(); - for (ConcurrentSkipListMap.Node n = loNode(); - isBeforeEnd(n); + Comparator cmp = m.comparator; + for (ConcurrentSkipListMap.Node n = loNode(cmp); + isBeforeEnd(n, cmp); n = n.next) { V v = n.getValidValue(); if (v != null && value.equals(v)) @@ -2776,8 +2856,9 @@ public class ConcurrentSkipListMap extends AbstractMap } public void clear() { - for (ConcurrentSkipListMap.Node n = loNode(); - isBeforeEnd(n); + Comparator cmp = m.comparator; + for (ConcurrentSkipListMap.Node n = loNode(cmp); + isBeforeEnd(n, cmp); n = n.next) { if (n.getValidValue() != null) m.remove(n.key); @@ -2787,22 +2868,21 @@ public class ConcurrentSkipListMap extends AbstractMap /* ---------------- ConcurrentMap API methods -------------- */ public V putIfAbsent(K key, V value) { - checkKeyBounds(key); + checkKeyBounds(key, m.comparator); return m.putIfAbsent(key, value); } public boolean remove(Object key, Object value) { - K k = (K)key; - return inBounds(k) && m.remove(k, value); + return inBounds(key, m.comparator) && m.remove(key, value); } public boolean replace(K key, V oldValue, V newValue) { - checkKeyBounds(key); + checkKeyBounds(key, m.comparator); return m.replace(key, oldValue, newValue); } public V replace(K key, V value) { - checkKeyBounds(key); + checkKeyBounds(key, m.comparator); return m.replace(key, value); } @@ -2820,10 +2900,9 @@ public class ConcurrentSkipListMap extends AbstractMap * Utility to create submaps, where given bounds override * unbounded(null) ones and/or are checked against bounded ones. */ - private SubMap newSubMap(K fromKey, - boolean fromInclusive, - K toKey, - boolean toInclusive) { + SubMap newSubMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { + Comparator cmp = m.comparator; if (isDescending) { // flip senses K tk = fromKey; fromKey = toKey; @@ -2838,7 +2917,7 @@ public class ConcurrentSkipListMap extends AbstractMap fromInclusive = loInclusive; } else { - int c = m.compare(fromKey, lo); + int c = cpr(cmp, fromKey, lo); if (c < 0 || (c == 0 && !loInclusive && fromInclusive)) throw new IllegalArgumentException("key out of range"); } @@ -2849,7 +2928,7 @@ public class ConcurrentSkipListMap extends AbstractMap toInclusive = hiInclusive; } else { - int c = m.compare(toKey, hi); + int c = cpr(cmp, toKey, hi); if (c > 0 || (c == 0 && !hiInclusive && toInclusive)) throw new IllegalArgumentException("key out of range"); } @@ -2858,24 +2937,20 @@ public class ConcurrentSkipListMap extends AbstractMap toKey, toInclusive, isDescending); } - public SubMap subMap(K fromKey, - boolean fromInclusive, - K toKey, - boolean toInclusive) { + public SubMap subMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { if (fromKey == null || toKey == null) throw new NullPointerException(); return newSubMap(fromKey, fromInclusive, toKey, toInclusive); } - public SubMap headMap(K toKey, - boolean inclusive) { + public SubMap headMap(K toKey, boolean inclusive) { if (toKey == null) throw new NullPointerException(); return newSubMap(null, false, toKey, inclusive); } - public SubMap tailMap(K fromKey, - boolean inclusive) { + public SubMap tailMap(K fromKey, boolean inclusive) { if (fromKey == null) throw new NullPointerException(); return newSubMap(fromKey, inclusive, null, false); @@ -2996,8 +3071,9 @@ public class ConcurrentSkipListMap extends AbstractMap /** * Variant of main Iter class to traverse through submaps. + * Also serves as back-up Spliterator for views */ - abstract class SubMapIter implements Iterator { + abstract class SubMapIter implements Iterator, Spliterator { /** the last node returned by next() */ Node lastReturned; /** the next node to return from next(); */ @@ -3006,16 +3082,19 @@ public class ConcurrentSkipListMap extends AbstractMap V nextValue; SubMapIter() { + Comparator cmp = m.comparator; for (;;) { - next = isDescending ? hiNode() : loNode(); + next = isDescending ? hiNode(cmp) : loNode(cmp); if (next == null) break; Object x = next.value; if (x != null && x != next) { - if (! inBounds(next.key)) + if (! inBounds(next.key, cmp)) next = null; - else - nextValue = (V) x; + else { + @SuppressWarnings("unchecked") V vv = (V)x; + nextValue = vv; + } break; } } @@ -3036,32 +3115,38 @@ public class ConcurrentSkipListMap extends AbstractMap } private void ascend() { + Comparator cmp = m.comparator; for (;;) { next = next.next; if (next == null) break; Object x = next.value; if (x != null && x != next) { - if (tooHigh(next.key)) + if (tooHigh(next.key, cmp)) next = null; - else - nextValue = (V) x; + else { + @SuppressWarnings("unchecked") V vv = (V)x; + nextValue = vv; + } break; } } } private void descend() { + Comparator cmp = m.comparator; for (;;) { - next = m.findNear(lastReturned.key, LT); + next = m.findNear(lastReturned.key, LT, cmp); if (next == null) break; Object x = next.value; if (x != null && x != next) { - if (tooLow(next.key)) + if (tooLow(next.key, cmp)) next = null; - else - nextValue = (V) x; + else { + @SuppressWarnings("unchecked") V vv = (V)x; + nextValue = vv; + } break; } } @@ -3075,6 +3160,26 @@ public class ConcurrentSkipListMap extends AbstractMap lastReturned = null; } + public Spliterator trySplit() { + return null; + } + + public boolean tryAdvance(Consumer action) { + if (hasNext()) { + action.accept(next()); + return true; + } + return false; + } + + public void forEachRemaining(Consumer action) { + while (hasNext()) + action.accept(next()); + } + + public long estimateSize() { + return Long.MAX_VALUE; + } } final class SubMapValueIterator extends SubMapIter { @@ -3083,6 +3188,9 @@ public class ConcurrentSkipListMap extends AbstractMap advance(); return v; } + public int characteristics() { + return 0; + } } final class SubMapKeyIterator extends SubMapIter { @@ -3091,6 +3199,13 @@ public class ConcurrentSkipListMap extends AbstractMap advance(); return n.key; } + public int characteristics() { + return Spliterator.DISTINCT | Spliterator.ORDERED | + Spliterator.SORTED; + } + public final Comparator getComparator() { + return SubMap.this.comparator(); + } } final class SubMapEntryIterator extends SubMapIter> { @@ -3100,18 +3215,351 @@ public class ConcurrentSkipListMap extends AbstractMap advance(); return new AbstractMap.SimpleImmutableEntry(n.key, v); } + public int characteristics() { + return Spliterator.DISTINCT; + } + } + } + + // default Map method overrides + + public void forEach(BiConsumer action) { + if (action == null) throw new NullPointerException(); + V v; + for (Node n = findFirst(); n != null; n = n.next) { + if ((v = n.getValidValue()) != null) + action.accept(n.key, v); + } + } + + public void replaceAll(BiFunction function) { + if (function == null) throw new NullPointerException(); + V v; + for (Node n = findFirst(); n != null; n = n.next) { + while ((v = n.getValidValue()) != null) { + V r = function.apply(n.key, v); + if (r == null) throw new NullPointerException(); + if (n.casValue(v, r)) + break; + } + } + } + + /** + * Base class providing common structure for Spliterators. + * (Although not all that much common functionality; as usual for + * view classes, details annoyingly vary in key, value, and entry + * subclasses in ways that are not worth abstracting out for + * internal classes.) + * + * The basic split strategy is to recursively descend from top + * level, row by row, descending to next row when either split + * off, or the end of row is encountered. Control of the number of + * splits relies on some statistical estimation: The expected + * remaining number of elements of a skip list when advancing + * either across or down decreases by about 25%. To make this + * observation useful, we need to know initial size, which we + * don't. But we can just use Integer.MAX_VALUE so that we + * don't prematurely zero out while splitting. + */ + abstract static class CSLMSpliterator { + final Comparator comparator; + final K fence; // exclusive upper bound for keys, or null if to end + Index row; // the level to split out + Node current; // current traversal node; initialize at origin + int est; // pseudo-size estimate + CSLMSpliterator(Comparator comparator, Index row, + Node origin, K fence, int est) { + this.comparator = comparator; this.row = row; + this.current = origin; this.fence = fence; this.est = est; + } + + public final long estimateSize() { return (long)est; } + } + + static final class KeySpliterator extends CSLMSpliterator + implements Spliterator { + KeySpliterator(Comparator comparator, Index row, + Node origin, K fence, int est) { + super(comparator, row, origin, fence, est); + } + + public Spliterator trySplit() { + Node e; K ek; + Comparator cmp = comparator; + K f = fence; + if ((e = current) != null && (ek = e.key) != null) { + for (Index q = row; q != null; q = row = q.down) { + Index s; Node b, n; K sk; + if ((s = q.right) != null && (b = s.node) != null && + (n = b.next) != null && n.value != null && + (sk = n.key) != null && cpr(cmp, sk, ek) > 0 && + (f == null || cpr(cmp, sk, f) < 0)) { + current = n; + Index r = q.down; + row = (s.right != null) ? s : s.down; + est -= est >>> 2; + return new KeySpliterator(cmp, r, e, sk, est); + } + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + current = null; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) + break; + if ((v = e.value) != null && v != e) + action.accept(k); + } + } + + public boolean tryAdvance(Consumer action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) { + e = null; + break; + } + if ((v = e.value) != null && v != e) { + current = e.next; + action.accept(k); + return true; + } + } + current = e; + return false; + } + + public int characteristics() { + return Spliterator.DISTINCT | Spliterator.SORTED | + Spliterator.ORDERED | Spliterator.CONCURRENT | + Spliterator.NONNULL; + } + + public final Comparator getComparator() { + return comparator; + } + } + // factory method for KeySpliterator + final KeySpliterator keySpliterator() { + Comparator cmp = comparator; + for (;;) { // ensure h corresponds to origin p + HeadIndex h; Node p; + Node b = (h = head).node; + if ((p = b.next) == null || p.value != null) + return new KeySpliterator(cmp, h, p, null, (p == null) ? + 0 : Integer.MAX_VALUE); + p.helpDelete(b, p.next); + } + } + + static final class ValueSpliterator extends CSLMSpliterator + implements Spliterator { + ValueSpliterator(Comparator comparator, Index row, + Node origin, K fence, int est) { + super(comparator, row, origin, fence, est); + } + + public Spliterator trySplit() { + Node e; K ek; + Comparator cmp = comparator; + K f = fence; + if ((e = current) != null && (ek = e.key) != null) { + for (Index q = row; q != null; q = row = q.down) { + Index s; Node b, n; K sk; + if ((s = q.right) != null && (b = s.node) != null && + (n = b.next) != null && n.value != null && + (sk = n.key) != null && cpr(cmp, sk, ek) > 0 && + (f == null || cpr(cmp, sk, f) < 0)) { + current = n; + Index r = q.down; + row = (s.right != null) ? s : s.down; + est -= est >>> 2; + return new ValueSpliterator(cmp, r, e, sk, est); + } + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + current = null; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) + break; + if ((v = e.value) != null && v != e) { + @SuppressWarnings("unchecked") V vv = (V)v; + action.accept(vv); + } + } + } + + public boolean tryAdvance(Consumer action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) { + e = null; + break; + } + if ((v = e.value) != null && v != e) { + current = e.next; + @SuppressWarnings("unchecked") V vv = (V)v; + action.accept(vv); + return true; + } + } + current = e; + return false; + } + + public int characteristics() { + return Spliterator.CONCURRENT | Spliterator.NONNULL; + } + } + + // Almost the same as keySpliterator() + final ValueSpliterator valueSpliterator() { + Comparator cmp = comparator; + for (;;) { + HeadIndex h; Node p; + Node b = (h = head).node; + if ((p = b.next) == null || p.value != null) + return new ValueSpliterator(cmp, h, p, null, (p == null) ? + 0 : Integer.MAX_VALUE); + p.helpDelete(b, p.next); + } + } + + static final class EntrySpliterator extends CSLMSpliterator + implements Spliterator> { + EntrySpliterator(Comparator comparator, Index row, + Node origin, K fence, int est) { + super(comparator, row, origin, fence, est); + } + + public Spliterator> trySplit() { + Node e; K ek; + Comparator cmp = comparator; + K f = fence; + if ((e = current) != null && (ek = e.key) != null) { + for (Index q = row; q != null; q = row = q.down) { + Index s; Node b, n; K sk; + if ((s = q.right) != null && (b = s.node) != null && + (n = b.next) != null && n.value != null && + (sk = n.key) != null && cpr(cmp, sk, ek) > 0 && + (f == null || cpr(cmp, sk, f) < 0)) { + current = n; + Index r = q.down; + row = (s.right != null) ? s : s.down; + est -= est >>> 2; + return new EntrySpliterator(cmp, r, e, sk, est); + } + } + } + return null; + } + + public void forEachRemaining(Consumer> action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + current = null; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) + break; + if ((v = e.value) != null && v != e) { + @SuppressWarnings("unchecked") V vv = (V)v; + action.accept + (new AbstractMap.SimpleImmutableEntry(k, vv)); + } + } + } + + public boolean tryAdvance(Consumer> action) { + if (action == null) throw new NullPointerException(); + Comparator cmp = comparator; + K f = fence; + Node e = current; + for (; e != null; e = e.next) { + K k; Object v; + if ((k = e.key) != null && f != null && cpr(cmp, f, k) <= 0) { + e = null; + break; + } + if ((v = e.value) != null && v != e) { + current = e.next; + @SuppressWarnings("unchecked") V vv = (V)v; + action.accept + (new AbstractMap.SimpleImmutableEntry(k, vv)); + return true; + } + } + current = e; + return false; + } + + public int characteristics() { + return Spliterator.DISTINCT | Spliterator.SORTED | + Spliterator.ORDERED | Spliterator.CONCURRENT | + Spliterator.NONNULL; + } + + public final Comparator> getComparator() { + return comparator == null ? null : + Map.Entry.comparingByKey(comparator); + } + } + + // Almost the same as keySpliterator() + final EntrySpliterator entrySpliterator() { + Comparator cmp = comparator; + for (;;) { // almost same as key version + HeadIndex h; Node p; + Node b = (h = head).node; + if ((p = b.next) == null || p.value != null) + return new EntrySpliterator(cmp, h, p, null, (p == null) ? + 0 : Integer.MAX_VALUE); + p.helpDelete(b, p.next); } } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long headOffset; + private static final long SECONDARY; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class k = ConcurrentSkipListMap.class; headOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("head")); + Class tk = Thread.class; + SECONDARY = UNSAFE.objectFieldOffset + (tk.getDeclaredField("threadLocalRandomSecondarySeed")); + } catch (Exception e) { throw new Error(e); } diff --git a/src/share/classes/java/util/concurrent/ConcurrentSkipListSet.java b/src/share/classes/java/util/concurrent/ConcurrentSkipListSet.java index 8214b37c4fd84ab185d091d648214c2ada60a29e..4c41e388c8b37ea16504c1d8c0836432494e1ad8 100644 --- a/src/share/classes/java/util/concurrent/ConcurrentSkipListSet.java +++ b/src/share/classes/java/util/concurrent/ConcurrentSkipListSet.java @@ -34,7 +34,17 @@ */ package java.util.concurrent; -import java.util.*; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.Set; +import java.util.SortedSet; +import java.util.Spliterator; /** * A scalable concurrent {@link NavigableSet} implementation based on @@ -44,33 +54,33 @@ import java.util.*; * on which constructor is used. * *

    This implementation provides expected average log(n) time - * cost for the contains, add, and remove + * cost for the {@code contains}, {@code add}, and {@code remove} * operations and their variants. Insertion, removal, and access * operations safely execute concurrently by multiple threads. * Iterators are weakly consistent, returning elements * reflecting the state of the set at some point at or since the * creation of the iterator. They do not throw {@link - * ConcurrentModificationException}, and may proceed concurrently with - * other operations. Ascending ordered views and their iterators are - * faster than descending ones. + * java.util.ConcurrentModificationException}, and may proceed + * concurrently with other operations. Ascending ordered views and + * their iterators are faster than descending ones. * - *

    Beware that, unlike in most collections, the size + *

    Beware that, unlike in most collections, the {@code size} * method is not a constant-time operation. Because of the * asynchronous nature of these sets, determining the current number * of elements requires a traversal of the elements, and so may report * inaccurate results if this collection is modified during traversal. - * Additionally, the bulk operations addAll, - * removeAll, retainAll, containsAll, - * equals, and toArray are not guaranteed + * Additionally, the bulk operations {@code addAll}, + * {@code removeAll}, {@code retainAll}, {@code containsAll}, + * {@code equals}, and {@code toArray} are not guaranteed * to be performed atomically. For example, an iterator operating - * concurrently with an addAll operation might view only some + * concurrently with an {@code addAll} operation might view only some * of the added elements. * *

    This class and its iterators implement all of the * optional methods of the {@link Set} and {@link Iterator} * interfaces. Like most other concurrent collection implementations, - * this class does not permit the use of null elements, - * because null arguments and return values cannot be reliably + * this class does not permit the use of {@code null} elements, + * because {@code null} arguments and return values cannot be reliably * distinguished from the absence of elements. * *

    This class is a member of the @@ -90,7 +100,7 @@ public class ConcurrentSkipListSet /** * The underlying map. Uses Boolean.TRUE as value for each * element. This field is declared final for the sake of thread - * safety, which entails some ugliness in clone() + * safety, which entails some ugliness in clone(). */ private final ConcurrentNavigableMap m; @@ -107,7 +117,7 @@ public class ConcurrentSkipListSet * the specified comparator. * * @param comparator the comparator that will be used to order this set. - * If null, the {@linkplain Comparable natural + * If {@code null}, the {@linkplain Comparable natural * ordering} of the elements will be used. */ public ConcurrentSkipListSet(Comparator comparator) { @@ -120,7 +130,7 @@ public class ConcurrentSkipListSet * {@linkplain Comparable natural ordering}. * * @param c The elements that will comprise the new set - * @throws ClassCastException if the elements in c are + * @throws ClassCastException if the elements in {@code c} are * not {@link Comparable}, or are not mutually comparable * @throws NullPointerException if the specified collection or any * of its elements are null @@ -151,7 +161,7 @@ public class ConcurrentSkipListSet } /** - * Returns a shallow copy of this ConcurrentSkipListSet + * Returns a shallow copy of this {@code ConcurrentSkipListSet} * instance. (The elements themselves are not cloned.) * * @return a shallow copy of this set @@ -172,8 +182,8 @@ public class ConcurrentSkipListSet /** * Returns the number of elements in this set. If this set - * contains more than Integer.MAX_VALUE elements, it - * returns Integer.MAX_VALUE. + * contains more than {@code Integer.MAX_VALUE} elements, it + * returns {@code Integer.MAX_VALUE}. * *

    Beware that, unlike in most collections, this method is * NOT a constant-time operation. Because of the @@ -191,20 +201,20 @@ public class ConcurrentSkipListSet } /** - * Returns true if this set contains no elements. - * @return true if this set contains no elements + * Returns {@code true} if this set contains no elements. + * @return {@code true} if this set contains no elements */ public boolean isEmpty() { return m.isEmpty(); } /** - * Returns true if this set contains the specified element. - * More formally, returns true if and only if this set - * contains an element e such that o.equals(e). + * Returns {@code true} if this set contains the specified element. + * More formally, returns {@code true} if and only if this set + * contains an element {@code e} such that {@code o.equals(e)}. * * @param o object to be checked for containment in this set - * @return true if this set contains the specified element + * @return {@code true} if this set contains the specified element * @throws ClassCastException if the specified element cannot be * compared with the elements currently in this set * @throws NullPointerException if the specified element is null @@ -215,15 +225,15 @@ public class ConcurrentSkipListSet /** * Adds the specified element to this set if it is not already present. - * More formally, adds the specified element e to this set if - * the set contains no element e2 such that e.equals(e2). + * More formally, adds the specified element {@code e} to this set if + * the set contains no element {@code e2} such that {@code e.equals(e2)}. * If this set already contains the element, the call leaves the set - * unchanged and returns false. + * unchanged and returns {@code false}. * * @param e element to be added to this set - * @return true if this set did not already contain the + * @return {@code true} if this set did not already contain the * specified element - * @throws ClassCastException if e cannot be compared + * @throws ClassCastException if {@code e} cannot be compared * with the elements currently in this set * @throws NullPointerException if the specified element is null */ @@ -233,15 +243,15 @@ public class ConcurrentSkipListSet /** * Removes the specified element from this set if it is present. - * More formally, removes an element e such that - * o.equals(e), if this set contains such an element. - * Returns true if this set contained the element (or + * More formally, removes an element {@code e} such that + * {@code o.equals(e)}, if this set contains such an element. + * Returns {@code true} if this set contained the element (or * equivalently, if this set changed as a result of the call). * (This set will not contain the element once the call returns.) * * @param o object to be removed from this set, if present - * @return true if this set contained the specified element - * @throws ClassCastException if o cannot be compared + * @return {@code true} if this set contained the specified element + * @throws ClassCastException if {@code o} cannot be compared * with the elements currently in this set * @throws NullPointerException if the specified element is null */ @@ -279,7 +289,7 @@ public class ConcurrentSkipListSet /** * Compares the specified object with this set for equality. Returns - * true if the specified object is also a set, the two sets + * {@code true} if the specified object is also a set, the two sets * have the same size, and every member of the specified set is * contained in this set (or equivalently, every member of this set is * contained in the specified set). This definition ensures that the @@ -287,7 +297,7 @@ public class ConcurrentSkipListSet * set interface. * * @param o the object to be compared for equality with this set - * @return true if the specified object is equal to this set + * @return {@code true} if the specified object is equal to this set */ public boolean equals(Object o) { // Override AbstractSet version to avoid calling size() @@ -312,7 +322,7 @@ public class ConcurrentSkipListSet * value is the asymmetric set difference of the two sets. * * @param c collection containing elements to be removed from this set - * @return true if this set changed as a result of the call + * @return {@code true} if this set changed as a result of the call * @throws ClassCastException if the types of one or more elements in this * set are incompatible with the specified collection * @throws NullPointerException if the specified collection or any @@ -380,14 +390,14 @@ public class ConcurrentSkipListSet } /** - * @throws NoSuchElementException {@inheritDoc} + * @throws java.util.NoSuchElementException {@inheritDoc} */ public E first() { return m.firstKey(); } /** - * @throws NoSuchElementException {@inheritDoc} + * @throws java.util.NoSuchElementException {@inheritDoc} */ public E last() { return m.lastKey(); @@ -460,7 +470,7 @@ public class ConcurrentSkipListSet * reflected in the descending set, and vice-versa. * *

    The returned set has an ordering equivalent to - * {@link Collections#reverseOrder(Comparator) Collections.reverseOrder}(comparator()). + * {@link Collections#reverseOrder(Comparator) Collections.reverseOrder}{@code (comparator())}. * The expression {@code s.descendingSet().descendingSet()} returns a * view of {@code s} essentially equivalent to {@code s}. * @@ -470,6 +480,14 @@ public class ConcurrentSkipListSet return new ConcurrentSkipListSet(m.descendingMap()); } + @SuppressWarnings("unchecked") + public Spliterator spliterator() { + if (m instanceof ConcurrentSkipListMap) + return ((ConcurrentSkipListMap)m).keySpliterator(); + else + return (Spliterator)((ConcurrentSkipListMap.SubMap)m).keyIterator(); + } + // Support for resetting map in clone private void setMap(ConcurrentNavigableMap map) { UNSAFE.putObjectVolatile(this, mapOffset, map); diff --git a/src/share/classes/java/util/concurrent/CopyOnWriteArrayList.java b/src/share/classes/java/util/concurrent/CopyOnWriteArrayList.java index 67e21313f683e8b17d0741f55badda16cbbf7229..2ea321100c31beecd6c563f11751a05f9e84567c 100644 --- a/src/share/classes/java/util/concurrent/CopyOnWriteArrayList.java +++ b/src/share/classes/java/util/concurrent/CopyOnWriteArrayList.java @@ -1,5 +1,4 @@ /* - * Copyright (c) 2003, 2012, 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 @@ -34,7 +33,19 @@ */ package java.util.concurrent; -import java.util.*; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.function.Predicate; @@ -42,10 +53,10 @@ import java.util.function.UnaryOperator; /** * A thread-safe variant of {@link java.util.ArrayList} in which all mutative - * operations (add, set, and so on) are implemented by + * operations ({@code add}, {@code set}, and so on) are implemented by * making a fresh copy of the underlying array. * - *

    This is ordinarily too costly, but may be more efficient + *

    This is ordinarily too costly, but may be more efficient * than alternatives when traversal operations vastly outnumber * mutations, and is useful when you cannot or don't want to * synchronize traversals, yet need to preclude interference among @@ -53,14 +64,14 @@ import java.util.function.UnaryOperator; * reference to the state of the array at the point that the iterator * was created. This array never changes during the lifetime of the * iterator, so interference is impossible and the iterator is - * guaranteed not to throw ConcurrentModificationException. + * guaranteed not to throw {@code ConcurrentModificationException}. * The iterator will not reflect additions, removals, or changes to * the list since the iterator was created. Element-changing - * operations on iterators themselves (remove, set, and - * add) are not supported. These methods throw - * UnsupportedOperationException. + * operations on iterators themselves ({@code remove}, {@code set}, and + * {@code add}) are not supported. These methods throw + * {@code UnsupportedOperationException}. * - *

    All elements are permitted, including null. + *

    All elements are permitted, including {@code null}. * *

    Memory consistency effects: As with other concurrent * collections, actions in a thread prior to placing an object into a @@ -82,10 +93,10 @@ public class CopyOnWriteArrayList private static final long serialVersionUID = 8673264195747942595L; /** The lock protecting all mutators */ - transient final ReentrantLock lock = new ReentrantLock(); + final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ - private volatile transient Object[] array; + private transient volatile Object[] array; /** * Gets the array. Non-private so as to also be accessible @@ -118,10 +129,15 @@ public class CopyOnWriteArrayList * @throws NullPointerException if the specified collection is null */ public CopyOnWriteArrayList(Collection c) { - Object[] elements = c.toArray(); - // c.toArray might (incorrectly) not return Object[] (see 6260652) - if (elements.getClass() != Object[].class) - elements = Arrays.copyOf(elements, elements.length, Object[].class); + Object[] elements; + if (c.getClass() == CopyOnWriteArrayList.class) + elements = ((CopyOnWriteArrayList)c).getArray(); + else { + elements = c.toArray(); + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elements.getClass() != Object[].class) + elements = Arrays.copyOf(elements, elements.length, Object[].class); + } setArray(elements); } @@ -146,9 +162,9 @@ public class CopyOnWriteArrayList } /** - * Returns true if this list contains no elements. + * Returns {@code true} if this list contains no elements. * - * @return true if this list contains no elements + * @return {@code true} if this list contains no elements */ public boolean isEmpty() { return size() == 0; @@ -158,7 +174,7 @@ public class CopyOnWriteArrayList * Tests for equality, coping with nulls. */ private static boolean eq(Object o1, Object o2) { - return (o1 == null ? o2 == null : o1.equals(o2)); + return (o1 == null) ? o2 == null : o1.equals(o2); } /** @@ -205,13 +221,13 @@ public class CopyOnWriteArrayList } /** - * Returns true if this list contains the specified element. - * More formally, returns true if and only if this list contains - * at least one element e such that + * Returns {@code true} if this list contains the specified element. + * More formally, returns {@code true} if and only if this list contains + * at least one element {@code e} such that * (o==null ? e==null : o.equals(e)). * * @param o element whose presence in this list is to be tested - * @return true if this list contains the specified element + * @return {@code true} if this list contains the specified element */ public boolean contains(Object o) { Object[] elements = getArray(); @@ -228,17 +244,17 @@ public class CopyOnWriteArrayList /** * Returns the index of the first occurrence of the specified element in - * this list, searching forwards from index, or returns -1 if + * this list, searching forwards from {@code index}, or returns -1 if * the element is not found. - * More formally, returns the lowest index i such that + * More formally, returns the lowest index {@code i} such that * (i >= index && (e==null ? get(i)==null : e.equals(get(i)))), * or -1 if there is no such index. * * @param e element to search for * @param index index to start searching from * @return the index of the first occurrence of the element in - * this list at position index or later in the list; - * -1 if the element is not found. + * this list at position {@code index} or later in the list; + * {@code -1} if the element is not found. * @throws IndexOutOfBoundsException if the specified index is negative */ public int indexOf(E e, int index) { @@ -256,16 +272,16 @@ public class CopyOnWriteArrayList /** * Returns the index of the last occurrence of the specified element in - * this list, searching backwards from index, or returns -1 if + * this list, searching backwards from {@code index}, or returns -1 if * the element is not found. - * More formally, returns the highest index i such that + * More formally, returns the highest index {@code i} such that * (i <= index && (e==null ? get(i)==null : e.equals(get(i)))), * or -1 if there is no such index. * * @param e element to search for * @param index index to start searching backwards from * @return the index of the last occurrence of the element at position - * less than or equal to index in this list; + * less than or equal to {@code index} in this list; * -1 if the element is not found. * @throws IndexOutOfBoundsException if the specified index is greater * than or equal to the current size of this list @@ -323,7 +339,7 @@ public class CopyOnWriteArrayList *

    If this list fits in the specified array with room to spare * (i.e., the array has more elements than this list), the element in * the array immediately following the end of the list is set to - * null. (This is useful in determining the length of this + * {@code null}. (This is useful in determining the length of this * list only if the caller knows that this list does not contain * any null elements.) * @@ -332,14 +348,14 @@ public class CopyOnWriteArrayList * precise control over the runtime type of the output array, and may, * under certain circumstances, be used to save allocation costs. * - *

    Suppose x is a list known to contain only strings. + *

    Suppose {@code x} is a list known to contain only strings. * The following code can be used to dump the list into a newly - * allocated array of String: + * allocated array of {@code String}: * *

     {@code String[] y = x.toArray(new String[0]);}
    * - * Note that toArray(new Object[0]) is identical in function to - * toArray(). + * Note that {@code toArray(new Object[0])} is identical in function to + * {@code toArray()}. * * @param a the array into which the elements of the list are to * be stored, if it is big enough; otherwise, a new array of the @@ -412,7 +428,7 @@ public class CopyOnWriteArrayList * Appends the specified element to the end of this list. * * @param e element to be appended to this list - * @return true (as specified by {@link Collection#add}) + * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { final ReentrantLock lock = this.lock; @@ -496,45 +512,54 @@ public class CopyOnWriteArrayList * Removes the first occurrence of the specified element from this list, * if it is present. If this list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index - * i such that + * {@code i} such that * (o==null ? get(i)==null : o.equals(get(i))) - * (if such an element exists). Returns true if this list + * (if such an element exists). Returns {@code true} if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present - * @return true if this list contained the specified element + * @return {@code true} if this list contained the specified element */ public boolean remove(Object o) { + Object[] snapshot = getArray(); + int index = indexOf(o, snapshot, 0, snapshot.length); + return (index < 0) ? false : remove(o, snapshot, index); + } + + /** + * A version of remove(Object) using the strong hint that given + * recent snapshot contains o at the given index. + */ + private boolean remove(Object o, Object[] snapshot, int index) { final ReentrantLock lock = this.lock; lock.lock(); try { - Object[] elements = getArray(); - int len = elements.length; - if (len != 0) { - // Copy while searching for element to remove - // This wins in the normal case of element being present - int newlen = len - 1; - Object[] newElements = new Object[newlen]; - - for (int i = 0; i < newlen; ++i) { - if (eq(o, elements[i])) { - // found one; copy remaining and exit - for (int k = i + 1; k < len; ++k) - newElements[k-1] = elements[k]; - setArray(newElements); - return true; - } else - newElements[i] = elements[i]; - } - - // special handling for last cell - if (eq(o, elements[newlen])) { - setArray(newElements); - return true; + Object[] current = getArray(); + int len = current.length; + if (snapshot != current) findIndex: { + int prefix = Math.min(index, len); + for (int i = 0; i < prefix; i++) { + if (current[i] != snapshot[i] && eq(o, current[i])) { + index = i; + break findIndex; + } } + if (index >= len) + return false; + if (current[index] == o) + break findIndex; + index = indexOf(o, current, index, len); + if (index < 0) + return false; } - return false; + Object[] newElements = new Object[len - 1]; + System.arraycopy(current, 0, newElements, 0, index); + System.arraycopy(current, index + 1, + newElements, index, + len - index - 1); + setArray(newElements); + return true; } finally { lock.unlock(); } @@ -542,10 +567,10 @@ public class CopyOnWriteArrayList /** * Removes from this list all of the elements whose index is between - * fromIndex, inclusive, and toIndex, exclusive. + * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. * Shifts any succeeding elements to the left (reduces their index). - * This call shortens the list by (toIndex - fromIndex) elements. - * (If toIndex==fromIndex, this operation has no effect.) + * This call shortens the list by {@code (toIndex - fromIndex)} elements. + * (If {@code toIndex==fromIndex}, this operation has no effect.) * * @param fromIndex index of first element to be removed * @param toIndex index after last element to be removed @@ -581,23 +606,34 @@ public class CopyOnWriteArrayList * Appends the element, if not present. * * @param e element to be added to this list, if absent - * @return true if the element was added + * @return {@code true} if the element was added */ public boolean addIfAbsent(E e) { + Object[] snapshot = getArray(); + return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : + addIfAbsent(e, snapshot); + } + + /** + * A version of addIfAbsent using the strong hint that given + * recent snapshot does not contain e. + */ + private boolean addIfAbsent(E e, Object[] snapshot) { final ReentrantLock lock = this.lock; lock.lock(); try { - // Copy while checking if already present. - // This wins in the most common case where it is not present - Object[] elements = getArray(); - int len = elements.length; - Object[] newElements = new Object[len + 1]; - for (int i = 0; i < len; ++i) { - if (eq(e, elements[i])) - return false; // exit, throwing away copy - else - newElements[i] = elements[i]; + Object[] current = getArray(); + int len = current.length; + if (snapshot != current) { + // Optimize for lost race to another addXXX operation + int common = Math.min(snapshot.length, len); + for (int i = 0; i < common; i++) + if (current[i] != snapshot[i] && eq(e, current[i])) + return false; + if (indexOf(e, current, common, len) >= 0) + return false; } + Object[] newElements = Arrays.copyOf(current, len + 1); newElements[len] = e; setArray(newElements); return true; @@ -607,11 +643,11 @@ public class CopyOnWriteArrayList } /** - * Returns true if this list contains all of the elements of the + * Returns {@code true} if this list contains all of the elements of the * specified collection. * * @param c collection to be checked for containment in this list - * @return true if this list contains all of the elements of the + * @return {@code true} if this list contains all of the elements of the * specified collection * @throws NullPointerException if the specified collection is null * @see #contains(Object) @@ -632,7 +668,7 @@ public class CopyOnWriteArrayList * in this class because of the need for an internal temporary array. * * @param c collection containing elements to be removed from this list - * @return true if this list changed as a result of the call + * @return {@code true} if this list changed as a result of the call * @throws ClassCastException if the class of an element of this list * is incompatible with the specified collection * (optional) @@ -675,7 +711,7 @@ public class CopyOnWriteArrayList * its elements that are not contained in the specified collection. * * @param c collection containing elements to be retained in this list - * @return true if this list changed as a result of the call + * @return {@code true} if this list changed as a result of the call * @throws ClassCastException if the class of an element of this list * is incompatible with the specified collection * (optional) @@ -727,22 +763,22 @@ public class CopyOnWriteArrayList Object[] cs = c.toArray(); if (cs.length == 0) return 0; - Object[] uniq = new Object[cs.length]; final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; int added = 0; - for (int i = 0; i < cs.length; ++i) { // scan for duplicates + // uniquify and compact elements in cs + for (int i = 0; i < cs.length; ++i) { Object e = cs[i]; if (indexOf(e, elements, 0, len) < 0 && - indexOf(e, uniq, 0, added) < 0) - uniq[added++] = e; + indexOf(e, cs, 0, added) < 0) + cs[added++] = e; } if (added > 0) { Object[] newElements = Arrays.copyOf(elements, len + added); - System.arraycopy(uniq, 0, newElements, len, added); + System.arraycopy(cs, 0, newElements, len, added); setArray(newElements); } return added; @@ -771,12 +807,13 @@ public class CopyOnWriteArrayList * collection's iterator. * * @param c collection containing elements to be added to this list - * @return true if this list changed as a result of the call + * @return {@code true} if this list changed as a result of the call * @throws NullPointerException if the specified collection is null * @see #add(Object) */ public boolean addAll(Collection c) { - Object[] cs = c.toArray(); + Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ? + ((CopyOnWriteArrayList)c).getArray() : c.toArray(); if (cs.length == 0) return false; final ReentrantLock lock = this.lock; @@ -784,9 +821,13 @@ public class CopyOnWriteArrayList try { Object[] elements = getArray(); int len = elements.length; - Object[] newElements = Arrays.copyOf(elements, len + cs.length); - System.arraycopy(cs, 0, newElements, len, cs.length); - setArray(newElements); + if (len == 0 && cs.getClass() == Object[].class) + setArray(cs); + else { + Object[] newElements = Arrays.copyOf(elements, len + cs.length); + System.arraycopy(cs, 0, newElements, len, cs.length); + setArray(newElements); + } return true; } finally { lock.unlock(); @@ -804,7 +845,7 @@ public class CopyOnWriteArrayList * @param index index at which to insert the first element * from the specified collection * @param c collection containing elements to be added to this list - * @return true if this list changed as a result of the call + * @return {@code true} if this list changed as a result of the call * @throws IndexOutOfBoundsException {@inheritDoc} * @throws NullPointerException if the specified collection is null * @see #add(int,Object) @@ -840,6 +881,74 @@ public class CopyOnWriteArrayList } } + public void forEach(Consumer action) { + if (action == null) throw new NullPointerException(); + Object[] elements = getArray(); + int len = elements.length; + for (int i = 0; i < len; ++i) { + @SuppressWarnings("unchecked") E e = (E) elements[i]; + action.accept(e); + } + } + + public boolean removeIf(Predicate filter) { + if (filter == null) throw new NullPointerException(); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + Object[] elements = getArray(); + int len = elements.length; + if (len != 0) { + int newlen = 0; + Object[] temp = new Object[len]; + for (int i = 0; i < len; ++i) { + @SuppressWarnings("unchecked") E e = (E) elements[i]; + if (!filter.test(e)) + temp[newlen++] = e; + } + if (newlen != len) { + setArray(Arrays.copyOf(temp, newlen)); + return true; + } + } + return false; + } finally { + lock.unlock(); + } + } + + public void replaceAll(UnaryOperator operator) { + if (operator == null) throw new NullPointerException(); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + Object[] elements = getArray(); + int len = elements.length; + Object[] newElements = Arrays.copyOf(elements, len); + for (int i = 0; i < len; ++i) { + @SuppressWarnings("unchecked") E e = (E) elements[i]; + newElements[i] = operator.apply(e); + } + setArray(newElements); + } finally { + lock.unlock(); + } + } + + public void sort(Comparator c) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + Object[] elements = getArray(); + Object[] newElements = Arrays.copyOf(elements, elements.length); + @SuppressWarnings("unchecked") E[] es = (E[])newElements; + Arrays.sort(es, c); + setArray(newElements); + } finally { + lock.unlock(); + } + } + /** * Saves this list to a stream (that is, serializes it). * @@ -886,8 +995,8 @@ public class CopyOnWriteArrayList * Returns a string representation of this list. The string * representation consists of the string representations of the list's * elements in the order they are returned by its iterator, enclosed in - * square brackets ("[]"). Adjacent elements are separated by - * the characters ", " (comma and space). Elements are + * square brackets ({@code "[]"}). Adjacent elements are separated by + * the characters {@code ", "} (comma and space). Elements are * converted to strings as by {@link String#valueOf(Object)}. * * @return a string representation of this list @@ -953,7 +1062,7 @@ public class CopyOnWriteArrayList *

    The returned iterator provides a snapshot of the state of the list * when the iterator was constructed. No synchronization is needed while * traversing the iterator. The iterator does NOT support the - * remove method. + * {@code remove} method. * * @return an iterator over the elements in this list in proper sequence */ @@ -967,7 +1076,7 @@ public class CopyOnWriteArrayList *

    The returned iterator provides a snapshot of the state of the list * when the iterator was constructed. No synchronization is needed while * traversing the iterator. The iterator does NOT support the - * remove, set or add methods. + * {@code remove}, {@code set} or {@code add} methods. */ public ListIterator listIterator() { return new COWIterator(getArray(), 0); @@ -979,7 +1088,7 @@ public class CopyOnWriteArrayList *

    The returned iterator provides a snapshot of the state of the list * when the iterator was constructed. No synchronization is needed while * traversing the iterator. The iterator does NOT support the - * remove, set or add methods. + * {@code remove}, {@code set} or {@code add} methods. * * @throws IndexOutOfBoundsException {@inheritDoc} */ @@ -992,7 +1101,12 @@ public class CopyOnWriteArrayList return new COWIterator(elements, index); } - private static class COWIterator implements ListIterator { + public Spliterator spliterator() { + return Spliterators.spliterator + (getArray(), Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + + static final class COWIterator implements ListIterator { /** Snapshot of the array */ private final Object[] snapshot; /** Index of element to be returned by subsequent call to next. */ @@ -1035,7 +1149,7 @@ public class CopyOnWriteArrayList /** * Not supported. Always throws UnsupportedOperationException. - * @throws UnsupportedOperationException always; remove + * @throws UnsupportedOperationException always; {@code remove} * is not supported by this iterator. */ public void remove() { @@ -1044,7 +1158,7 @@ public class CopyOnWriteArrayList /** * Not supported. Always throws UnsupportedOperationException. - * @throws UnsupportedOperationException always; set + * @throws UnsupportedOperationException always; {@code set} * is not supported by this iterator. */ public void set(E e) { @@ -1053,7 +1167,7 @@ public class CopyOnWriteArrayList /** * Not supported. Always throws UnsupportedOperationException. - * @throws UnsupportedOperationException always; add + * @throws UnsupportedOperationException always; {@code add} * is not supported by this iterator. */ public void add(E e) { @@ -1061,12 +1175,13 @@ public class CopyOnWriteArrayList } @Override - @SuppressWarnings("unchecked") public void forEachRemaining(Consumer action) { Objects.requireNonNull(action); - final int size = snapshot.length; - for (int i=cursor; i < size; i++) { - action.accept((E) snapshot[i]); + Object[] elements = snapshot; + final int size = elements.length; + for (int i = cursor; i < size; i++) { + @SuppressWarnings("unchecked") E e = (E) elements[i]; + action.accept(e); } cursor = size; } @@ -1074,7 +1189,7 @@ public class CopyOnWriteArrayList /** * Returns a view of the portion of this list between - * fromIndex, inclusive, and toIndex, exclusive. + * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. * The returned list is backed by this list, so changes in the * returned list are reflected in this list. * @@ -1274,55 +1389,196 @@ public class CopyOnWriteArrayList } } - @Override public void forEach(Consumer action) { - @SuppressWarnings("unchecked") - final E[] elements = (E[]) l.getArray(); - checkForComodification(); - l.forEach(action, elements, offset, offset + size); + if (action == null) throw new NullPointerException(); + int lo = offset; + int hi = offset + size; + Object[] a = expectedArray; + if (l.getArray() != a) + throw new ConcurrentModificationException(); + if (lo < 0 || hi > a.length) + throw new IndexOutOfBoundsException(); + for (int i = lo; i < hi; ++i) { + @SuppressWarnings("unchecked") E e = (E) a[i]; + action.accept(e); + } + } + + public void replaceAll(UnaryOperator operator) { + if (operator == null) throw new NullPointerException(); + final ReentrantLock lock = l.lock; + lock.lock(); + try { + int lo = offset; + int hi = offset + size; + Object[] elements = expectedArray; + if (l.getArray() != elements) + throw new ConcurrentModificationException(); + int len = elements.length; + if (lo < 0 || hi > len) + throw new IndexOutOfBoundsException(); + Object[] newElements = Arrays.copyOf(elements, len); + for (int i = lo; i < hi; ++i) { + @SuppressWarnings("unchecked") E e = (E) elements[i]; + newElements[i] = operator.apply(e); + } + l.setArray(expectedArray = newElements); + } finally { + lock.unlock(); + } } - @Override public void sort(Comparator c) { final ReentrantLock lock = l.lock; lock.lock(); try { - checkForComodification(); - l.sort(c, offset, offset + size); - expectedArray = l.getArray(); + int lo = offset; + int hi = offset + size; + Object[] elements = expectedArray; + if (l.getArray() != elements) + throw new ConcurrentModificationException(); + int len = elements.length; + if (lo < 0 || hi > len) + throw new IndexOutOfBoundsException(); + Object[] newElements = Arrays.copyOf(elements, len); + @SuppressWarnings("unchecked") E[] es = (E[])newElements; + Arrays.sort(es, lo, hi, c); + l.setArray(expectedArray = newElements); } finally { lock.unlock(); } } - @Override - public boolean removeIf(Predicate filter) { - Objects.requireNonNull(filter); + public boolean removeAll(Collection c) { + if (c == null) throw new NullPointerException(); + boolean removed = false; final ReentrantLock lock = l.lock; lock.lock(); try { - checkForComodification(); - final int removeCount = - l.removeIf(filter, offset, offset + size); - expectedArray = l.getArray(); - size -= removeCount; - return removeCount > 0; + int n = size; + if (n > 0) { + int lo = offset; + int hi = offset + n; + Object[] elements = expectedArray; + if (l.getArray() != elements) + throw new ConcurrentModificationException(); + int len = elements.length; + if (lo < 0 || hi > len) + throw new IndexOutOfBoundsException(); + int newSize = 0; + Object[] temp = new Object[n]; + for (int i = lo; i < hi; ++i) { + Object element = elements[i]; + if (!c.contains(element)) + temp[newSize++] = element; + } + if (newSize != n) { + Object[] newElements = new Object[len - n + newSize]; + System.arraycopy(elements, 0, newElements, 0, lo); + System.arraycopy(temp, 0, newElements, lo, newSize); + System.arraycopy(elements, hi, newElements, + lo + newSize, len - hi); + size = newSize; + removed = true; + l.setArray(expectedArray = newElements); + } + } } finally { lock.unlock(); } + return removed; } - @Override - public void replaceAll(UnaryOperator operator) { + public boolean retainAll(Collection c) { + if (c == null) throw new NullPointerException(); + boolean removed = false; final ReentrantLock lock = l.lock; lock.lock(); try { - checkForComodification(); - l.replaceAll(operator, offset, offset + size); - expectedArray = l.getArray(); + int n = size; + if (n > 0) { + int lo = offset; + int hi = offset + n; + Object[] elements = expectedArray; + if (l.getArray() != elements) + throw new ConcurrentModificationException(); + int len = elements.length; + if (lo < 0 || hi > len) + throw new IndexOutOfBoundsException(); + int newSize = 0; + Object[] temp = new Object[n]; + for (int i = lo; i < hi; ++i) { + Object element = elements[i]; + if (c.contains(element)) + temp[newSize++] = element; + } + if (newSize != n) { + Object[] newElements = new Object[len - n + newSize]; + System.arraycopy(elements, 0, newElements, 0, lo); + System.arraycopy(temp, 0, newElements, lo, newSize); + System.arraycopy(elements, hi, newElements, + lo + newSize, len - hi); + size = newSize; + removed = true; + l.setArray(expectedArray = newElements); + } + } } finally { lock.unlock(); } + return removed; + } + + public boolean removeIf(Predicate filter) { + if (filter == null) throw new NullPointerException(); + boolean removed = false; + final ReentrantLock lock = l.lock; + lock.lock(); + try { + int n = size; + if (n > 0) { + int lo = offset; + int hi = offset + n; + Object[] elements = expectedArray; + if (l.getArray() != elements) + throw new ConcurrentModificationException(); + int len = elements.length; + if (lo < 0 || hi > len) + throw new IndexOutOfBoundsException(); + int newSize = 0; + Object[] temp = new Object[n]; + for (int i = lo; i < hi; ++i) { + @SuppressWarnings("unchecked") E e = (E) elements[i]; + if (!filter.test(e)) + temp[newSize++] = e; + } + if (newSize != n) { + Object[] newElements = new Object[len - n + newSize]; + System.arraycopy(elements, 0, newElements, 0, lo); + System.arraycopy(temp, 0, newElements, lo, newSize); + System.arraycopy(elements, hi, newElements, + lo + newSize, len - hi); + size = newSize; + removed = true; + l.setArray(expectedArray = newElements); + } + } + } finally { + lock.unlock(); + } + return removed; + } + + public Spliterator spliterator() { + int lo = offset; + int hi = offset + size; + Object[] a = expectedArray; + if (l.getArray() != a) + throw new ConcurrentModificationException(); + if (lo < 0 || hi > a.length) + throw new IndexOutOfBoundsException(); + return Spliterators.spliterator + (a, lo, hi, Spliterator.IMMUTABLE | Spliterator.ORDERED); } } @@ -1380,11 +1636,12 @@ public class CopyOnWriteArrayList } @Override - @SuppressWarnings("unchecked") public void forEachRemaining(Consumer action) { Objects.requireNonNull(action); - while (nextIndex() < size) { - action.accept(it.next()); + int s = size; + ListIterator i = it; + while (nextIndex() < s) { + action.accept(i.next()); } } } @@ -1405,139 +1662,4 @@ public class CopyOnWriteArrayList throw new Error(e); } } - - @Override - @SuppressWarnings("unchecked") - public void forEach(Consumer action) { - forEach(action, (E[]) getArray(), 0, size()); - } - - private void forEach(Consumer action, - final E[] elements, - final int from, final int to) { - Objects.requireNonNull(action); - for (int i = from; i < to; i++) { - action.accept(elements[i]); - } - } - - @Override - public void sort(Comparator c) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - sort(c, 0, size()); - } finally { - lock.unlock(); - } - } - - // must be called with this.lock held - @SuppressWarnings("unchecked") - private void sort(Comparator c, final int from, final int to) { - final E[] elements = (E[]) getArray(); - final E[] newElements = Arrays.copyOf(elements, elements.length); - // only elements [from, to) are sorted - Arrays.sort(newElements, from, to, c); - setArray(newElements); - } - - @Override - public boolean removeIf(Predicate filter) { - Objects.requireNonNull(filter); - final ReentrantLock lock = this.lock; - lock.lock(); - try { - return removeIf(filter, 0, size()) > 0; - } finally { - lock.unlock(); - } - } - - // must be called with this.lock held - private int removeIf(Predicate filter, final int from, final int to) { - Objects.requireNonNull(filter); - final ReentrantLock lock = this.lock; - lock.lock(); - try { - @SuppressWarnings("unchecked") - final E[] elements = (E[]) getArray(); - - // figure out which elements are to be removed - // any exception thrown from the filter predicate at this stage - // will leave the collection unmodified - int removeCount = 0; - final int range = to - from; - final BitSet removeSet = new BitSet(range); - for (int i = 0; i < range; i++) { - final E element = elements[from + i]; - if (filter.test(element)) { - // removeSet is zero-based to keep its size small - removeSet.set(i); - removeCount++; - } - } - - // copy surviving elements into a new array - if (removeCount > 0) { - final int newSize = elements.length - removeCount; - final int newRange = newSize - from; - @SuppressWarnings("unchecked") - final E[] newElements = (E[]) new Object[newSize]; - // copy elements before [from, to) unmodified - for (int i = 0; i < from; i++) { - newElements[i] = elements[i]; - } - // elements [from, to) are subject to removal - int j = 0; - for (int i = 0; (i < range) && (j < newRange); i++) { - i = removeSet.nextClearBit(i); - if (i >= range) { - break; - } - newElements[from + (j++)] = elements[from + i]; - } - // copy any remaining elements beyond [from, to) - j += from; - for (int i = to; (i < elements.length) && (j < newSize); i++) { - newElements[j++] = elements[i]; - } - setArray(newElements); - } - - return removeCount; - } finally { - lock.unlock(); - } - } - - @Override - public void replaceAll(UnaryOperator operator) { - Objects.requireNonNull(operator); - final ReentrantLock lock = this.lock; - lock.lock(); - try { - replaceAll(operator, 0, size()); - } finally { - lock.unlock(); - } - } - - // must be called with this.lock held - @SuppressWarnings("unchecked") - private void replaceAll(UnaryOperator operator, final int from, final int to) { - final E[] elements = (E[]) getArray(); - final E[] newElements = (E[]) new Object[elements.length]; - for (int i = 0; i < from; i++) { - newElements[i] = elements[i]; - } - // the operator is only applied to elements [from, to) - for (int i = from; i < to; i++) { - newElements[i] = operator.apply(elements[i]); - } - for (int i = to; i < elements.length; i++) { - newElements[i] = elements[i]; - } - setArray(newElements); - } } diff --git a/src/share/classes/java/util/concurrent/CopyOnWriteArraySet.java b/src/share/classes/java/util/concurrent/CopyOnWriteArraySet.java index da3c98563320bfc441bb9572065a05160d709335..ffb9668647cfd5ed00f15b88a7509889bdea9b9d 100644 --- a/src/share/classes/java/util/concurrent/CopyOnWriteArraySet.java +++ b/src/share/classes/java/util/concurrent/CopyOnWriteArraySet.java @@ -34,7 +34,14 @@ */ package java.util.concurrent; -import java.util.*; +import java.util.Collection; +import java.util.Set; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Predicate; +import java.util.function.Consumer; /** * A {@link java.util.Set} that uses an internal {@link CopyOnWriteArrayList} @@ -45,17 +52,17 @@ import java.util.*; * vastly outnumber mutative operations, and you need * to prevent interference among threads during traversal. *

  • It is thread-safe. - *
  • Mutative operations (add, set, remove, etc.) + *
  • Mutative operations ({@code add}, {@code set}, {@code remove}, etc.) * are expensive since they usually entail copying the entire underlying * array. - *
  • Iterators do not support the mutative remove operation. + *
  • Iterators do not support the mutative {@code remove} operation. *
  • Traversal via iterators is fast and cannot encounter * interference from other threads. Iterators rely on * unchanging snapshots of the array at the time the iterators were * constructed. *
* - *

Sample Usage. The following code sketch uses a + *

Sample Usage. The following code sketch uses a * copy-on-write set to maintain a set of Handler objects that * perform some action upon state updates. * @@ -73,7 +80,7 @@ import java.util.*; * public void update() { * changeState(); * for (Handler handler : handlers) - * handler.handle(); + * handler.handle(); * } * }} * @@ -107,8 +114,15 @@ public class CopyOnWriteArraySet extends AbstractSet * @throws NullPointerException if the specified collection is null */ public CopyOnWriteArraySet(Collection c) { - al = new CopyOnWriteArrayList(); - al.addAllAbsent(c); + if (c.getClass() == CopyOnWriteArraySet.class) { + @SuppressWarnings("unchecked") CopyOnWriteArraySet cc = + (CopyOnWriteArraySet)c; + al = new CopyOnWriteArrayList(cc.al); + } + else { + al = new CopyOnWriteArrayList(); + al.addAllAbsent(c); + } } /** @@ -121,22 +135,22 @@ public class CopyOnWriteArraySet extends AbstractSet } /** - * Returns true if this set contains no elements. + * Returns {@code true} if this set contains no elements. * - * @return true if this set contains no elements + * @return {@code true} if this set contains no elements */ public boolean isEmpty() { return al.isEmpty(); } /** - * Returns true if this set contains the specified element. - * More formally, returns true if and only if this set - * contains an element e such that + * Returns {@code true} if this set contains the specified element. + * More formally, returns {@code true} if and only if this set + * contains an element {@code e} such that * (o==null ? e==null : o.equals(e)). * * @param o element whose presence in this set is to be tested - * @return true if this set contains the specified element + * @return {@code true} if this set contains the specified element */ public boolean contains(Object o) { return al.contains(o); @@ -172,7 +186,7 @@ public class CopyOnWriteArraySet extends AbstractSet *

If this set fits in the specified array with room to spare * (i.e., the array has more elements than this set), the element in * the array immediately following the end of the set is set to - * null. (This is useful in determining the length of this + * {@code null}. (This is useful in determining the length of this * set only if the caller knows that this set does not contain * any null elements.) * @@ -185,14 +199,14 @@ public class CopyOnWriteArraySet extends AbstractSet * precise control over the runtime type of the output array, and may, * under certain circumstances, be used to save allocation costs. * - *

Suppose x is a set known to contain only strings. + *

Suppose {@code x} is a set known to contain only strings. * The following code can be used to dump the set into a newly allocated - * array of String: + * array of {@code String}: * *

 {@code String[] y = x.toArray(new String[0]);}
* - * Note that toArray(new Object[0]) is identical in function to - * toArray(). + * Note that {@code toArray(new Object[0])} is identical in function to + * {@code toArray()}. * * @param a the array into which the elements of this set are to be * stored, if it is big enough; otherwise, a new array of the same @@ -217,15 +231,15 @@ public class CopyOnWriteArraySet extends AbstractSet /** * Removes the specified element from this set if it is present. - * More formally, removes an element e such that + * More formally, removes an element {@code e} such that * (o==null ? e==null : o.equals(e)), - * if this set contains such an element. Returns true if + * if this set contains such an element. Returns {@code true} if * this set contained the element (or equivalently, if this set * changed as a result of the call). (This set will not contain the * element once the call returns.) * * @param o object to be removed from this set, if present - * @return true if this set contained the specified element + * @return {@code true} if this set contained the specified element */ public boolean remove(Object o) { return al.remove(o); @@ -233,14 +247,14 @@ public class CopyOnWriteArraySet extends AbstractSet /** * Adds the specified element to this set if it is not already present. - * More formally, adds the specified element e to this set if - * the set contains no element e2 such that + * More formally, adds the specified element {@code e} to this set if + * the set contains no element {@code e2} such that * (e==null ? e2==null : e.equals(e2)). * If this set already contains the element, the call leaves the set - * unchanged and returns false. + * unchanged and returns {@code false}. * * @param e element to be added to this set - * @return true if this set did not already contain the specified + * @return {@code true} if this set did not already contain the specified * element */ public boolean add(E e) { @@ -248,12 +262,12 @@ public class CopyOnWriteArraySet extends AbstractSet } /** - * Returns true if this set contains all of the elements of the + * Returns {@code true} if this set contains all of the elements of the * specified collection. If the specified collection is also a set, this - * method returns true if it is a subset of this set. + * method returns {@code true} if it is a subset of this set. * * @param c collection to be checked for containment in this set - * @return true if this set contains all of the elements of the + * @return {@code true} if this set contains all of the elements of the * specified collection * @throws NullPointerException if the specified collection is null * @see #contains(Object) @@ -265,13 +279,13 @@ public class CopyOnWriteArraySet extends AbstractSet /** * Adds all of the elements in the specified collection to this set if * they're not already present. If the specified collection is also a - * set, the addAll operation effectively modifies this set so + * set, the {@code addAll} operation effectively modifies this set so * that its value is the union of the two sets. The behavior of * this operation is undefined if the specified collection is modified * while the operation is in progress. * * @param c collection containing elements to be added to this set - * @return true if this set changed as a result of the call + * @return {@code true} if this set changed as a result of the call * @throws NullPointerException if the specified collection is null * @see #add(Object) */ @@ -286,7 +300,7 @@ public class CopyOnWriteArraySet extends AbstractSet * asymmetric set difference of the two sets. * * @param c collection containing elements to be removed from this set - * @return true if this set changed as a result of the call + * @return {@code true} if this set changed as a result of the call * @throws ClassCastException if the class of an element of this set * is incompatible with the specified collection (optional) * @throws NullPointerException if this set contains a null element and the @@ -307,7 +321,7 @@ public class CopyOnWriteArraySet extends AbstractSet * two sets. * * @param c collection containing elements to be retained in this set - * @return true if this set changed as a result of the call + * @return {@code true} if this set changed as a result of the call * @throws ClassCastException if the class of an element of this set * is incompatible with the specified collection (optional) * @throws NullPointerException if this set contains a null element and the @@ -326,7 +340,7 @@ public class CopyOnWriteArraySet extends AbstractSet *

The returned iterator provides a snapshot of the state of the set * when the iterator was constructed. No synchronization is needed while * traversing the iterator. The iterator does NOT support the - * remove method. + * {@code remove} method. * * @return an iterator over the elements in this set */ @@ -338,7 +352,7 @@ public class CopyOnWriteArraySet extends AbstractSet * Compares the specified object with this set for equality. * Returns {@code true} if the specified object is the same object * as this object, or if it is also a {@link Set} and the elements - * returned by an {@linkplain List#iterator() iterator} over the + * returned by an {@linkplain Set#iterator() iterator} over the * specified set are the same as the elements returned by an * iterator over this set. More formally, the two iterators are * considered to return the same elements if they return the same @@ -382,6 +396,19 @@ public class CopyOnWriteArraySet extends AbstractSet return k == len; } + public boolean removeIf(Predicate filter) { + return al.removeIf(filter); + } + + public void forEach(Consumer action) { + al.forEach(action); + } + + public Spliterator spliterator() { + return Spliterators.spliterator + (al.getArray(), Spliterator.IMMUTABLE | Spliterator.DISTINCT); + } + /** * Tests for equality, coping with nulls. */ diff --git a/src/share/classes/java/util/concurrent/CountDownLatch.java b/src/share/classes/java/util/concurrent/CountDownLatch.java index 055eb113727c37f3563c22f462e730eee4238261..a8b50ca39079b6b62a55fd26d180c93ac7ad28ea 100644 --- a/src/share/classes/java/util/concurrent/CountDownLatch.java +++ b/src/share/classes/java/util/concurrent/CountDownLatch.java @@ -92,15 +92,15 @@ import java.util.concurrent.locks.AbstractQueuedSynchronizer; * private final CountDownLatch startSignal; * private final CountDownLatch doneSignal; * Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { - * this.startSignal = startSignal; - * this.doneSignal = doneSignal; + * this.startSignal = startSignal; + * this.doneSignal = doneSignal; * } * public void run() { - * try { - * startSignal.await(); - * doWork(); - * doneSignal.countDown(); - * } catch (InterruptedException ex) {} // return; + * try { + * startSignal.await(); + * doWork(); + * doneSignal.countDown(); + * } catch (InterruptedException ex) {} // return; * } * * void doWork() { ... } @@ -130,14 +130,14 @@ import java.util.concurrent.locks.AbstractQueuedSynchronizer; * private final CountDownLatch doneSignal; * private final int i; * WorkerRunnable(CountDownLatch doneSignal, int i) { - * this.doneSignal = doneSignal; - * this.i = i; + * this.doneSignal = doneSignal; + * this.i = i; * } * public void run() { - * try { - * doWork(i); - * doneSignal.countDown(); - * } catch (InterruptedException ex) {} // return; + * try { + * doWork(i); + * doneSignal.countDown(); + * } catch (InterruptedException ex) {} // return; * } * * void doWork() { ... } diff --git a/src/share/classes/java/util/concurrent/CyclicBarrier.java b/src/share/classes/java/util/concurrent/CyclicBarrier.java index eb25879dbcf8b5146741fe6670361dce45c71c3e..d1186d8eb4f4e76bc8545ef5925549082b9271d7 100644 --- a/src/share/classes/java/util/concurrent/CyclicBarrier.java +++ b/src/share/classes/java/util/concurrent/CyclicBarrier.java @@ -45,14 +45,14 @@ import java.util.concurrent.locks.ReentrantLock; * cyclic because it can be re-used after the waiting threads * are released. * - *

A CyclicBarrier supports an optional {@link Runnable} command + *

A {@code CyclicBarrier} supports an optional {@link Runnable} command * that is run once per barrier point, after the last thread in the party * arrives, but before any threads are released. * This barrier action is useful * for updating shared-state before any of the parties continue. * - *

Sample usage: Here is an example of - * using a barrier in a parallel decomposition design: + *

Sample usage: Here is an example of using a barrier in a + * parallel decomposition design: * *

 {@code
  * class Solver {
@@ -81,16 +81,20 @@ import java.util.concurrent.locks.ReentrantLock;
  *   public Solver(float[][] matrix) {
  *     data = matrix;
  *     N = matrix.length;
- *     barrier = new CyclicBarrier(N,
- *                                 new Runnable() {
- *                                   public void run() {
- *                                     mergeRows(...);
- *                                   }
- *                                 });
- *     for (int i = 0; i < N; ++i)
- *       new Thread(new Worker(i)).start();
+ *     Runnable barrierAction =
+ *       new Runnable() { public void run() { mergeRows(...); }};
+ *     barrier = new CyclicBarrier(N, barrierAction);
  *
- *     waitUntilDone();
+ *     List threads = new ArrayList(N);
+ *     for (int i = 0; i < N; i++) {
+ *       Thread thread = new Thread(new Worker(i));
+ *       threads.add(thread);
+ *       thread.start();
+ *     }
+ *
+ *     // wait until done
+ *     for (Thread thread : threads)
+ *       thread.join();
  *   }
  * }}
* @@ -98,8 +102,8 @@ import java.util.concurrent.locks.ReentrantLock; * barrier until all rows have been processed. When all rows are processed * the supplied {@link Runnable} barrier action is executed and merges the * rows. If the merger - * determines that a solution has been found then done() will return - * true and each worker will terminate. + * determines that a solution has been found then {@code done()} will return + * {@code true} and each worker will terminate. * *

If the barrier action does not rely on the parties being suspended when * it is executed, then any of the threads in the party could execute that @@ -112,7 +116,7 @@ import java.util.concurrent.locks.ReentrantLock; * // log the completion of this iteration * }} * - *

The CyclicBarrier uses an all-or-none breakage model + *

The {@code CyclicBarrier} uses an all-or-none breakage model * for failed synchronization attempts: If a thread leaves a barrier * point prematurely because of interruption, failure, or timeout, all * other threads waiting at that barrier point will also leave @@ -139,7 +143,7 @@ public class CyclicBarrier { * is reset. There can be many generations associated with threads * using the barrier - due to the non-deterministic way the lock * may be allocated to waiting threads - but only one of these - * can be active at a time (the one to which count applies) + * can be active at a time (the one to which {@code count} applies) * and all the rest are either broken or tripped. * There need not be an active generation if there has been a break * but no subsequent reset. @@ -259,7 +263,7 @@ public class CyclicBarrier { } /** - * Creates a new CyclicBarrier that will trip when the + * Creates a new {@code CyclicBarrier} that will trip when the * given number of parties (threads) are waiting upon it, and which * will execute the given barrier action when the barrier is tripped, * performed by the last thread entering the barrier. @@ -278,7 +282,7 @@ public class CyclicBarrier { } /** - * Creates a new CyclicBarrier that will trip when the + * Creates a new {@code CyclicBarrier} that will trip when the * given number of parties (threads) are waiting upon it, and * does not perform a predefined action when the barrier is tripped. * @@ -301,7 +305,7 @@ public class CyclicBarrier { /** * Waits until all {@linkplain #getParties parties} have invoked - * await on this barrier. + * {@code await} on this barrier. * *

If the current thread is not the last to arrive then it is * disabled for thread scheduling purposes and lies dormant until @@ -326,7 +330,7 @@ public class CyclicBarrier { * *

If the barrier is {@link #reset} while any thread is waiting, * or if the barrier {@linkplain #isBroken is broken} when - * await is invoked, or while any thread is waiting, then + * {@code await} is invoked, or while any thread is waiting, then * {@link BrokenBarrierException} is thrown. * *

If any thread is {@linkplain Thread#interrupt interrupted} while waiting, @@ -343,7 +347,7 @@ public class CyclicBarrier { * the broken state. * * @return the arrival index of the current thread, where index - * {@link #getParties()} - 1 indicates the first + * {@code getParties() - 1} indicates the first * to arrive and zero indicates the last to arrive * @throws InterruptedException if the current thread was interrupted * while waiting @@ -351,7 +355,7 @@ public class CyclicBarrier { * interrupted or timed out while the current thread was * waiting, or the barrier was reset, or the barrier was * broken when {@code await} was called, or the barrier - * action (if present) failed due an exception. + * action (if present) failed due to an exception */ public int await() throws InterruptedException, BrokenBarrierException { try { @@ -363,7 +367,7 @@ public class CyclicBarrier { /** * Waits until all {@linkplain #getParties parties} have invoked - * await on this barrier, or the specified waiting time elapses. + * {@code await} on this barrier, or the specified waiting time elapses. * *

If the current thread is not the last to arrive then it is * disabled for thread scheduling purposes and lies dormant until @@ -393,7 +397,7 @@ public class CyclicBarrier { * *

If the barrier is {@link #reset} while any thread is waiting, * or if the barrier {@linkplain #isBroken is broken} when - * await is invoked, or while any thread is waiting, then + * {@code await} is invoked, or while any thread is waiting, then * {@link BrokenBarrierException} is thrown. * *

If any thread is {@linkplain Thread#interrupt interrupted} while @@ -412,16 +416,17 @@ public class CyclicBarrier { * @param timeout the time to wait for the barrier * @param unit the time unit of the timeout parameter * @return the arrival index of the current thread, where index - * {@link #getParties()} - 1 indicates the first + * {@code getParties() - 1} indicates the first * to arrive and zero indicates the last to arrive * @throws InterruptedException if the current thread was interrupted * while waiting - * @throws TimeoutException if the specified timeout elapses + * @throws TimeoutException if the specified timeout elapses. + * In this case the barrier will be broken. * @throws BrokenBarrierException if another thread was * interrupted or timed out while the current thread was * waiting, or the barrier was reset, or the barrier was broken * when {@code await} was called, or the barrier action (if - * present) failed due an exception + * present) failed due to an exception */ public int await(long timeout, TimeUnit unit) throws InterruptedException, diff --git a/src/share/classes/java/util/concurrent/DelayQueue.java b/src/share/classes/java/util/concurrent/DelayQueue.java index aa68b749d7b19c2388a8069017f65444f9787f98..610fb717ac80a88df6632748edc28f72b4a7ac03 100644 --- a/src/share/classes/java/util/concurrent/DelayQueue.java +++ b/src/share/classes/java/util/concurrent/DelayQueue.java @@ -33,28 +33,31 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ - package java.util.concurrent; -import java.util.concurrent.locks.*; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; import java.util.*; /** * An unbounded {@linkplain BlockingQueue blocking queue} of - * Delayed elements, in which an element can only be taken + * {@code Delayed} elements, in which an element can only be taken * when its delay has expired. The head of the queue is that - * Delayed element whose delay expired furthest in the - * past. If no delay has expired there is no head and poll - * will return null. Expiration occurs when an element's - * getDelay(TimeUnit.NANOSECONDS) method returns a value less + * {@code Delayed} element whose delay expired furthest in the + * past. If no delay has expired there is no head and {@code poll} + * will return {@code null}. Expiration occurs when an element's + * {@code getDelay(TimeUnit.NANOSECONDS)} method returns a value less * than or equal to zero. Even though unexpired elements cannot be - * removed using take or poll, they are otherwise - * treated as normal elements. For example, the size method + * removed using {@code take} or {@code poll}, they are otherwise + * treated as normal elements. For example, the {@code size} method * returns the count of both expired and unexpired elements. * This queue does not permit null elements. * *

This class and its iterator implement all of the * optional methods of the {@link Collection} and {@link - * Iterator} interfaces. + * Iterator} interfaces. The Iterator provided in method {@link + * #iterator()} is not guaranteed to traverse the elements of + * the DelayQueue in any particular order. * *

This class is a member of the * @@ -64,11 +67,10 @@ import java.util.*; * @author Doug Lea * @param the type of elements held in this collection */ - public class DelayQueue extends AbstractQueue implements BlockingQueue { - private transient final ReentrantLock lock = new ReentrantLock(); + private final transient ReentrantLock lock = new ReentrantLock(); private final PriorityQueue q = new PriorityQueue(); /** @@ -97,12 +99,12 @@ public class DelayQueue extends AbstractQueue private final Condition available = lock.newCondition(); /** - * Creates a new DelayQueue that is initially empty. + * Creates a new {@code DelayQueue} that is initially empty. */ public DelayQueue() {} /** - * Creates a DelayQueue initially containing the elements of the + * Creates a {@code DelayQueue} initially containing the elements of the * given collection of {@link Delayed} instances. * * @param c the collection of elements to initially contain @@ -117,7 +119,7 @@ public class DelayQueue extends AbstractQueue * Inserts the specified element into this delay queue. * * @param e the element to add - * @return true (as specified by {@link Collection#add}) + * @return {@code true} (as specified by {@link Collection#add}) * @throws NullPointerException if the specified element is null */ public boolean add(E e) { @@ -128,7 +130,7 @@ public class DelayQueue extends AbstractQueue * Inserts the specified element into this delay queue. * * @param e the element to add - * @return true + * @return {@code true} * @throws NullPointerException if the specified element is null */ public boolean offer(E e) { @@ -164,7 +166,7 @@ public class DelayQueue extends AbstractQueue * @param e the element to add * @param timeout This parameter is ignored as the method never blocks * @param unit This parameter is ignored as the method never blocks - * @return true + * @return {@code true} * @throws NullPointerException {@inheritDoc} */ public boolean offer(E e, long timeout, TimeUnit unit) { @@ -172,10 +174,10 @@ public class DelayQueue extends AbstractQueue } /** - * Retrieves and removes the head of this queue, or returns null + * Retrieves and removes the head of this queue, or returns {@code null} * if this queue has no elements with an expired delay. * - * @return the head of this queue, or null if this + * @return the head of this queue, or {@code null} if this * queue has no elements with an expired delay */ public E poll() { @@ -183,7 +185,7 @@ public class DelayQueue extends AbstractQueue lock.lock(); try { E first = q.peek(); - if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0) + if (first == null || first.getDelay(NANOSECONDS) > 0) return null; else return q.poll(); @@ -208,10 +210,11 @@ public class DelayQueue extends AbstractQueue if (first == null) available.await(); else { - long delay = first.getDelay(TimeUnit.NANOSECONDS); + long delay = first.getDelay(NANOSECONDS); if (delay <= 0) return q.poll(); - else if (leader != null) + first = null; // don't retain ref while waiting + if (leader != null) available.await(); else { Thread thisThread = Thread.currentThread(); @@ -237,7 +240,7 @@ public class DelayQueue extends AbstractQueue * until an element with an expired delay is available on this queue, * or the specified wait time expires. * - * @return the head of this queue, or null if the + * @return the head of this queue, or {@code null} if the * specified waiting time elapses before an element with * an expired delay becomes available * @throws InterruptedException {@inheritDoc} @@ -255,11 +258,12 @@ public class DelayQueue extends AbstractQueue else nanos = available.awaitNanos(nanos); } else { - long delay = first.getDelay(TimeUnit.NANOSECONDS); + long delay = first.getDelay(NANOSECONDS); if (delay <= 0) return q.poll(); if (nanos <= 0) return null; + first = null; // don't retain ref while waiting if (nanos < delay || leader != null) nanos = available.awaitNanos(nanos); else { @@ -284,13 +288,13 @@ public class DelayQueue extends AbstractQueue /** * Retrieves, but does not remove, the head of this queue, or - * returns null if this queue is empty. Unlike - * poll, if no expired elements are available in the queue, + * returns {@code null} if this queue is empty. Unlike + * {@code poll}, if no expired elements are available in the queue, * this method returns the element that will expire next, * if one exists. * - * @return the head of this queue, or null if this - * queue is empty. + * @return the head of this queue, or {@code null} if this + * queue is empty */ public E peek() { final ReentrantLock lock = this.lock; @@ -312,6 +316,17 @@ public class DelayQueue extends AbstractQueue } } + /** + * Returns first element only if it is expired. + * Used only by drainTo. Call only when holding lock. + */ + private E peekExpired() { + // assert lock.isHeldByCurrentThread(); + E first = q.peek(); + return (first == null || first.getDelay(NANOSECONDS) > 0) ? + null : first; + } + /** * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} @@ -327,11 +342,9 @@ public class DelayQueue extends AbstractQueue lock.lock(); try { int n = 0; - for (;;) { - E first = q.peek(); - if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0) - break; - c.add(q.poll()); + for (E e; (e = peekExpired()) != null;) { + c.add(e); // In this order, in case add() throws. + q.poll(); ++n; } return n; @@ -357,11 +370,9 @@ public class DelayQueue extends AbstractQueue lock.lock(); try { int n = 0; - while (n < maxElements) { - E first = q.peek(); - if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0) - break; - c.add(q.poll()); + for (E e; n < maxElements && (e = peekExpired()) != null;) { + c.add(e); // In this order, in case add() throws. + q.poll(); ++n; } return n; @@ -387,10 +398,10 @@ public class DelayQueue extends AbstractQueue } /** - * Always returns Integer.MAX_VALUE because - * a DelayQueue is not capacity constrained. + * Always returns {@code Integer.MAX_VALUE} because + * a {@code DelayQueue} is not capacity constrained. * - * @return Integer.MAX_VALUE + * @return {@code Integer.MAX_VALUE} */ public int remainingCapacity() { return Integer.MAX_VALUE; @@ -430,7 +441,7 @@ public class DelayQueue extends AbstractQueue *

If this queue fits in the specified array with room to spare * (i.e., the array has more elements than this queue), the element in * the array immediately following the end of the queue is set to - * null. + * {@code null}. * *

Like the {@link #toArray()} method, this method acts as bridge between * array-based and collection-based APIs. Further, this method allows @@ -438,13 +449,12 @@ public class DelayQueue extends AbstractQueue * under certain circumstances, be used to save allocation costs. * *

The following code can be used to dump a delay queue into a newly - * allocated array of Delayed: + * allocated array of {@code Delayed}: * - *

-     *     Delayed[] a = q.toArray(new Delayed[0]);
+ *
 {@code Delayed[] a = q.toArray(new Delayed[0]);}
* - * Note that toArray(new Object[0]) is identical in function to - * toArray(). + * Note that {@code toArray(new Object[0])} is identical in function to + * {@code toArray()}. * * @param a the array into which the elements of the queue are to * be stored, if it is big enough; otherwise, a new array of the @@ -479,6 +489,24 @@ public class DelayQueue extends AbstractQueue } } + /** + * Identity-based version for use in Itr.remove + */ + void removeEQ(Object o) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + for (Iterator it = q.iterator(); it.hasNext(); ) { + if (o == it.next()) { + it.remove(); + break; + } + } + } finally { + lock.unlock(); + } + } + /** * Returns an iterator over all the elements (both expired and * unexpired) in this queue. The iterator does not return the @@ -502,7 +530,7 @@ public class DelayQueue extends AbstractQueue */ private class Itr implements Iterator { final Object[] array; // Array of all elements - int cursor; // index of next element to return; + int cursor; // index of next element to return int lastRet; // index of last element, or -1 if no such Itr(Object[] array) { @@ -525,21 +553,8 @@ public class DelayQueue extends AbstractQueue public void remove() { if (lastRet < 0) throw new IllegalStateException(); - Object x = array[lastRet]; + removeEQ(array[lastRet]); lastRet = -1; - // Traverse underlying queue to find == element, - // not just a .equals element. - lock.lock(); - try { - for (Iterator it = q.iterator(); it.hasNext(); ) { - if (it.next() == x) { - it.remove(); - return; - } - } - } finally { - lock.unlock(); - } } } diff --git a/src/share/classes/java/util/concurrent/Delayed.java b/src/share/classes/java/util/concurrent/Delayed.java index 8b185e1d79cd1e20299eb674347080dcd9db4d3c..5da1ec8d94cc03665be92774cc872068d61e798a 100644 --- a/src/share/classes/java/util/concurrent/Delayed.java +++ b/src/share/classes/java/util/concurrent/Delayed.java @@ -40,8 +40,8 @@ package java.util.concurrent; * acted upon after a given delay. * *

An implementation of this interface must define a - * compareTo method that provides an ordering consistent with - * its getDelay method. + * {@code compareTo} method that provides an ordering consistent with + * its {@code getDelay} method. * * @since 1.5 * @author Doug Lea diff --git a/src/share/classes/java/util/concurrent/Exchanger.java b/src/share/classes/java/util/concurrent/Exchanger.java index 5accdb1ce58f73c9a3b24f85132b57f318910c8b..980b0e187a4b7316a8bfc9e071190ba26882d3e4 100644 --- a/src/share/classes/java/util/concurrent/Exchanger.java +++ b/src/share/classes/java/util/concurrent/Exchanger.java @@ -35,7 +35,8 @@ */ package java.util.concurrent; -import java.util.concurrent.atomic.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; /** @@ -52,7 +53,7 @@ import java.util.concurrent.locks.LockSupport; * to swap buffers between threads so that the thread filling the * buffer gets a freshly emptied one when it needs it, handing off the * filled one to the thread emptying the buffer. - *

{@code
+ *  
 {@code
  * class FillAndEmpty {
  *   Exchanger exchanger = new Exchanger();
  *   DataBuffer initialEmptyBuffer = ... a made-up type
@@ -88,8 +89,7 @@ import java.util.concurrent.locks.LockSupport;
  *     new Thread(new FillingLoop()).start();
  *     new Thread(new EmptyingLoop()).start();
  *   }
- * }
- * }
+ * }}
* *

Memory consistency effects: For each pair of threads that * successfully exchange objects via an {@code Exchanger}, actions @@ -103,486 +103,425 @@ import java.util.concurrent.locks.LockSupport; * @param The type of objects that may be exchanged */ public class Exchanger { + /* - * Algorithm Description: + * Overview: The core algorithm is, for an exchange "slot", + * and a participant (caller) with an item: + * + * for (;;) { + * if (slot is empty) { // offer + * place item in a Node; + * if (can CAS slot from empty to node) { + * wait for release; + * return matching item in node; + * } + * } + * else if (can CAS slot from node to empty) { // release + * get the item in node; + * set matching item in node; + * release waiting thread; + * } + * // else retry on CAS failure + * } + * + * This is among the simplest forms of a "dual data structure" -- + * see Scott and Scherer's DISC 04 paper and + * http://www.cs.rochester.edu/research/synchronization/pseudocode/duals.html * - * The basic idea is to maintain a "slot", which is a reference to - * a Node containing both an Item to offer and a "hole" waiting to - * get filled in. If an incoming "occupying" thread sees that the - * slot is null, it CAS'es (compareAndSets) a Node there and waits - * for another to invoke exchange. That second "fulfilling" thread - * sees that the slot is non-null, and so CASes it back to null, - * also exchanging items by CASing the hole, plus waking up the - * occupying thread if it is blocked. In each case CAS'es may - * fail because a slot at first appears non-null but is null upon - * CAS, or vice-versa. So threads may need to retry these - * actions. + * This works great in principle. But in practice, like many + * algorithms centered on atomic updates to a single location, it + * scales horribly when there are more than a few participants + * using the same Exchanger. So the implementation instead uses a + * form of elimination arena, that spreads out this contention by + * arranging that some threads typically use different slots, + * while still ensuring that eventually, any two parties will be + * able to exchange items. That is, we cannot completely partition + * across threads, but instead give threads arena indices that + * will on average grow under contention and shrink under lack of + * contention. We approach this by defining the Nodes that we need + * anyway as ThreadLocals, and include in them per-thread index + * and related bookkeeping state. (We can safely reuse per-thread + * nodes rather than creating them fresh each time because slots + * alternate between pointing to a node vs null, so cannot + * encounter ABA problems. However, we do need some care in + * resetting them between uses.) * - * This simple approach works great when there are only a few - * threads using an Exchanger, but performance rapidly - * deteriorates due to CAS contention on the single slot when - * there are lots of threads using an exchanger. So instead we use - * an "arena"; basically a kind of hash table with a dynamically - * varying number of slots, any one of which can be used by - * threads performing an exchange. Incoming threads pick slots - * based on a hash of their Thread ids. If an incoming thread - * fails to CAS in its chosen slot, it picks an alternative slot - * instead. And similarly from there. If a thread successfully - * CASes into a slot but no other thread arrives, it tries - * another, heading toward the zero slot, which always exists even - * if the table shrinks. The particular mechanics controlling this - * are as follows: + * Implementing an effective arena requires allocating a bunch of + * space, so we only do so upon detecting contention (except on + * uniprocessors, where they wouldn't help, so aren't used). + * Otherwise, exchanges use the single-slot slotExchange method. + * On contention, not only must the slots be in different + * locations, but the locations must not encounter memory + * contention due to being on the same cache line (or more + * generally, the same coherence unit). Because, as of this + * writing, there is no way to determine cacheline size, we define + * a value that is enough for common platforms. Additionally, + * extra care elsewhere is taken to avoid other false/unintended + * sharing and to enhance locality, including adding padding (via + * sun.misc.Contended) to Nodes, embedding "bound" as an Exchanger + * field, and reworking some park/unpark mechanics compared to + * LockSupport versions. * - * Waiting: Slot zero is special in that it is the only slot that - * exists when there is no contention. A thread occupying slot - * zero will block if no thread fulfills it after a short spin. - * In other cases, occupying threads eventually give up and try - * another slot. Waiting threads spin for a while (a period that - * should be a little less than a typical context-switch time) - * before either blocking (if slot zero) or giving up (if other - * slots) and restarting. There is no reason for threads to block - * unless there are unlikely to be any other threads present. - * Occupants are mainly avoiding memory contention so sit there - * quietly polling for a shorter period than it would take to - * block and then unblock them. Non-slot-zero waits that elapse - * because of lack of other threads waste around one extra - * context-switch time per try, which is still on average much - * faster than alternative approaches. + * The arena starts out with only one used slot. We expand the + * effective arena size by tracking collisions; i.e., failed CASes + * while trying to exchange. By nature of the above algorithm, the + * only kinds of collision that reliably indicate contention are + * when two attempted releases collide -- one of two attempted + * offers can legitimately fail to CAS without indicating + * contention by more than one other thread. (Note: it is possible + * but not worthwhile to more precisely detect contention by + * reading slot values after CAS failures.) When a thread has + * collided at each slot within the current arena bound, it tries + * to expand the arena size by one. We track collisions within + * bounds by using a version (sequence) number on the "bound" + * field, and conservatively reset collision counts when a + * participant notices that bound has been updated (in either + * direction). * - * Sizing: Usually, using only a few slots suffices to reduce - * contention. Especially with small numbers of threads, using - * too many slots can lead to just as poor performance as using - * too few of them, and there's not much room for error. The - * variable "max" maintains the number of slots actually in - * use. It is increased when a thread sees too many CAS - * failures. (This is analogous to resizing a regular hash table - * based on a target load factor, except here, growth steps are - * just one-by-one rather than proportional.) Growth requires - * contention failures in each of three tried slots. Requiring - * multiple failures for expansion copes with the fact that some - * failed CASes are not due to contention but instead to simple - * races between two threads or thread pre-emptions occurring - * between reading and CASing. Also, very transient peak - * contention can be much higher than the average sustainable - * levels. An attempt to decrease the max limit is usually made - * when a non-slot-zero wait elapses without being fulfilled. - * Threads experiencing elapsed waits move closer to zero, so - * eventually find existing (or future) threads even if the table - * has been shrunk due to inactivity. The chosen mechanics and - * thresholds for growing and shrinking are intrinsically - * entangled with indexing and hashing inside the exchange code, - * and can't be nicely abstracted out. + * The effective arena size is reduced (when there is more than + * one slot) by giving up on waiting after a while and trying to + * decrement the arena size on expiration. The value of "a while" + * is an empirical matter. We implement by piggybacking on the + * use of spin->yield->block that is essential for reasonable + * waiting performance anyway -- in a busy exchanger, offers are + * usually almost immediately released, in which case context + * switching on multiprocessors is extremely slow/wasteful. Arena + * waits just omit the blocking part, and instead cancel. The spin + * count is empirically chosen to be a value that avoids blocking + * 99% of the time under maximum sustained exchange rates on a + * range of test machines. Spins and yields entail some limited + * randomness (using a cheap xorshift) to avoid regular patterns + * that can induce unproductive grow/shrink cycles. (Using a + * pseudorandom also helps regularize spin cycle duration by + * making branches unpredictable.) Also, during an offer, a + * waiter can "know" that it will be released when its slot has + * changed, but cannot yet proceed until match is set. In the + * mean time it cannot cancel the offer, so instead spins/yields. + * Note: It is possible to avoid this secondary check by changing + * the linearization point to be a CAS of the match field (as done + * in one case in the Scott & Scherer DISC paper), which also + * increases asynchrony a bit, at the expense of poorer collision + * detection and inability to always reuse per-thread nodes. So + * the current scheme is typically a better tradeoff. * - * Hashing: Each thread picks its initial slot to use in accord - * with a simple hashcode. The sequence is the same on each - * encounter by any given thread, but effectively random across - * threads. Using arenas encounters the classic cost vs quality - * tradeoffs of all hash tables. Here, we use a one-step FNV-1a - * hash code based on the current thread's Thread.getId(), along - * with a cheap approximation to a mod operation to select an - * index. The downside of optimizing index selection in this way - * is that the code is hardwired to use a maximum table size of - * 32. But this value more than suffices for known platforms and - * applications. + * On collisions, indices traverse the arena cyclically in reverse + * order, restarting at the maximum index (which will tend to be + * sparsest) when bounds change. (On expirations, indices instead + * are halved until reaching 0.) It is possible (and has been + * tried) to use randomized, prime-value-stepped, or double-hash + * style traversal instead of simple cyclic traversal to reduce + * bunching. But empirically, whatever benefits these may have + * don't overcome their added overhead: We are managing operations + * that occur very quickly unless there is sustained contention, + * so simpler/faster control policies work better than more + * accurate but slower ones. * - * Probing: On sensed contention of a selected slot, we probe - * sequentially through the table, analogously to linear probing - * after collision in a hash table. (We move circularly, in - * reverse order, to mesh best with table growth and shrinkage - * rules.) Except that to minimize the effects of false-alarms - * and cache thrashing, we try the first selected slot twice - * before moving. + * Because we use expiration for arena size control, we cannot + * throw TimeoutExceptions in the timed version of the public + * exchange method until the arena size has shrunken to zero (or + * the arena isn't enabled). This may delay response to timeout + * but is still within spec. * - * Padding: Even with contention management, slots are heavily - * contended, so use cache-padding to avoid poor memory - * performance. Because of this, slots are lazily constructed - * only when used, to avoid wasting this space unnecessarily. - * While isolation of locations is not much of an issue at first - * in an application, as time goes on and garbage-collectors - * perform compaction, slots are very likely to be moved adjacent - * to each other, which can cause much thrashing of cache lines on - * MPs unless padding is employed. + * Essentially all of the implementation is in methods + * slotExchange and arenaExchange. These have similar overall + * structure, but differ in too many details to combine. The + * slotExchange method uses the single Exchanger field "slot" + * rather than arena array elements. However, it still needs + * minimal collision detection to trigger arena construction. + * (The messiest part is making sure interrupt status and + * InterruptedExceptions come out right during transitions when + * both methods may be called. This is done by using null return + * as a sentinel to recheck interrupt status.) * - * This is an improvement of the algorithm described in the paper - * "A Scalable Elimination-based Exchange Channel" by William - * Scherer, Doug Lea, and Michael Scott in Proceedings of SCOOL05 - * workshop. Available at: http://hdl.handle.net/1802/2104 + * As is too common in this sort of code, methods are monolithic + * because most of the logic relies on reads of fields that are + * maintained as local variables so can't be nicely factored -- + * mainly, here, bulky spin->yield->block/cancel code), and + * heavily dependent on intrinsics (Unsafe) to use inlined + * embedded CAS and related memory access operations (that tend + * not to be as readily inlined by dynamic compilers when they are + * hidden behind other methods that would more nicely name and + * encapsulate the intended effects). This includes the use of + * putOrderedX to clear fields of the per-thread Nodes between + * uses. Note that field Node.item is not declared as volatile + * even though it is read by releasing threads, because they only + * do so after CAS operations that must precede access, and all + * uses by the owning thread are otherwise acceptably ordered by + * other operations. (Because the actual points of atomicity are + * slot CASes, it would also be legal for the write to Node.match + * in a release to be weaker than a full volatile write. However, + * this is not done because it could allow further postponement of + * the write, delaying progress.) */ - /** The number of CPUs, for sizing and spin control */ - private static final int NCPU = Runtime.getRuntime().availableProcessors(); - /** - * The capacity of the arena. Set to a value that provides more - * than enough space to handle contention. On small machines - * most slots won't be used, but it is still not wasted because - * the extra space provides some machine-level address padding - * to minimize interference with heavily CAS'ed Slot locations. - * And on very large machines, performance eventually becomes - * bounded by memory bandwidth, not numbers of threads/CPUs. - * This constant cannot be changed without also modifying - * indexing and hashing algorithms. + * The byte distance (as a shift value) between any two used slots + * in the arena. 1 << ASHIFT should be at least cacheline size. */ - private static final int CAPACITY = 32; + private static final int ASHIFT = 7; /** - * The value of "max" that will hold all threads without - * contention. When this value is less than CAPACITY, some - * otherwise wasted expansion can be avoided. + * The maximum supported arena index. The maximum allocatable + * arena size is MMASK + 1. Must be a power of two minus one, less + * than (1<<(31-ASHIFT)). The cap of 255 (0xff) more than suffices + * for the expected scaling limits of the main algorithms. */ - private static final int FULL = - Math.max(0, Math.min(CAPACITY, NCPU / 2) - 1); + private static final int MMASK = 0xff; /** - * The number of times to spin (doing nothing except polling a - * memory location) before blocking or giving up while waiting to - * be fulfilled. Should be zero on uniprocessors. On - * multiprocessors, this value should be large enough so that two - * threads exchanging items as fast as possible block only when - * one of them is stalled (due to GC or preemption), but not much - * longer, to avoid wasting CPU resources. Seen differently, this - * value is a little over half the number of cycles of an average - * context switch time on most systems. The value here is - * approximately the average of those across a range of tested - * systems. + * Unit for sequence/version bits of bound field. Each successful + * change to the bound also adds SEQ. */ - private static final int SPINS = (NCPU == 1) ? 0 : 2000; + private static final int SEQ = MMASK + 1; + + /** The number of CPUs, for sizing and spin control */ + private static final int NCPU = Runtime.getRuntime().availableProcessors(); /** - * The number of times to spin before blocking in timed waits. - * Timed waits spin more slowly because checking the time takes - * time. The best value relies mainly on the relative rate of - * System.nanoTime vs memory accesses. The value is empirically - * derived to work well across a variety of systems. + * The maximum slot index of the arena: The number of slots that + * can in principle hold all threads without contention, or at + * most the maximum indexable value. */ - private static final int TIMED_SPINS = SPINS / 20; + static final int FULL = (NCPU >= (MMASK << 1)) ? MMASK : NCPU >>> 1; /** - * Sentinel item representing cancellation of a wait due to - * interruption, timeout, or elapsed spin-waits. This value is - * placed in holes on cancellation, and used as a return value - * from waiting methods to indicate failure to set or get hole. + * The bound for spins while waiting for a match. The actual + * number of iterations will on average be about twice this value + * due to randomization. Note: Spinning is disabled when NCPU==1. */ - private static final Object CANCEL = new Object(); + private static final int SPINS = 1 << 10; /** * Value representing null arguments/returns from public - * methods. This disambiguates from internal requirement that - * holes start out as null to mean they are not yet set. + * methods. Needed because the API originally didn't disallow null + * arguments, which it should have. */ private static final Object NULL_ITEM = new Object(); /** - * Nodes hold partially exchanged data. This class - * opportunistically subclasses AtomicReference to represent the - * hole. So get() returns hole, and compareAndSet CAS'es value - * into hole. This class cannot be parameterized as "V" because - * of the use of non-V CANCEL sentinels. + * Sentinel value returned by internal exchange methods upon + * timeout, to avoid need for separate timed versions of these + * methods. */ - @SuppressWarnings("serial") - private static final class Node extends AtomicReference { - /** The element offered by the Thread creating this node. */ - public final Object item; - - /** The Thread waiting to be signalled; null until waiting. */ - public volatile Thread waiter; - - /** - * Creates node with given item and empty hole. - * @param item the item - */ - public Node(Object item) { - this.item = item; - } - } + private static final Object TIMED_OUT = new Object(); /** - * A Slot is an AtomicReference with heuristic padding to lessen - * cache effects of this heavily CAS'ed location. While the - * padding adds noticeable space, all slots are created only on - * demand, and there will be more than one of them only when it - * would improve throughput more than enough to outweigh using - * extra space. + * Nodes hold partially exchanged data, plus other per-thread + * bookkeeping. Padded via @sun.misc.Contended to reduce memory + * contention. */ - @SuppressWarnings("serial") - private static final class Slot extends AtomicReference { - // Improve likelihood of isolation on <= 64 byte cache lines - long q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, qa, qb, qc, qd, qe; + @sun.misc.Contended static final class Node { + int index; // Arena index + int bound; // Last recorded value of Exchanger.bound + int collides; // Number of CAS failures at current bound + int hash; // Pseudo-random for spins + Object item; // This thread's current item + volatile Object match; // Item provided by releasing thread + volatile Thread parked; // Set to this thread when parked, else null } - /** - * Slot array. Elements are lazily initialized when needed. - * Declared volatile to enable double-checked lazy construction. - */ - private volatile Slot[] arena = new Slot[CAPACITY]; - - /** - * The maximum slot index being used. The value sometimes - * increases when a thread experiences too many CAS contentions, - * and sometimes decreases when a spin-wait elapses. Changes - * are performed only via compareAndSet, to avoid stale values - * when a thread happens to stall right before setting. - */ - private final AtomicInteger max = new AtomicInteger(); - - /** - * Main exchange function, handling the different policy variants. - * Uses Object, not "V" as argument and return value to simplify - * handling of sentinel values. Callers from public methods decode - * and cast accordingly. - * - * @param item the (non-null) item to exchange - * @param timed true if the wait is timed - * @param nanos if timed, the maximum wait time - * @return the other thread's item, or CANCEL if interrupted or timed out - */ - private Object doExchange(Object item, boolean timed, long nanos) { - Node me = new Node(item); // Create in case occupying - int index = hashIndex(); // Index of current slot - int fails = 0; // Number of CAS failures - - for (;;) { - Object y; // Contents of current slot - Slot slot = arena[index]; - if (slot == null) // Lazily initialize slots - createSlot(index); // Continue loop to reread - else if ((y = slot.get()) != null && // Try to fulfill - slot.compareAndSet(y, null)) { - Node you = (Node)y; // Transfer item - if (you.compareAndSet(null, item)) { - LockSupport.unpark(you.waiter); - return you.item; - } // Else cancelled; continue - } - else if (y == null && // Try to occupy - slot.compareAndSet(null, me)) { - if (index == 0) // Blocking wait for slot 0 - return timed ? - awaitNanos(me, slot, nanos) : - await(me, slot); - Object v = spinWait(me, slot); // Spin wait for non-0 - if (v != CANCEL) - return v; - me = new Node(item); // Throw away cancelled node - int m = max.get(); - if (m > (index >>>= 1)) // Decrease index - max.compareAndSet(m, m - 1); // Maybe shrink table - } - else if (++fails > 1) { // Allow 2 fails on 1st slot - int m = max.get(); - if (fails > 3 && m < FULL && max.compareAndSet(m, m + 1)) - index = m + 1; // Grow on 3rd failed slot - else if (--index < 0) - index = m; // Circularly traverse - } - } - } - - /** - * Returns a hash index for the current thread. Uses a one-step - * FNV-1a hash code (http://www.isthe.com/chongo/tech/comp/fnv/) - * based on the current thread's Thread.getId(). These hash codes - * have more uniform distribution properties with respect to small - * moduli (here 1-31) than do other simple hashing functions. - * - *

To return an index between 0 and max, we use a cheap - * approximation to a mod operation, that also corrects for bias - * due to non-power-of-2 remaindering (see {@link - * java.util.Random#nextInt}). Bits of the hashcode are masked - * with "nbits", the ceiling power of two of table size (looked up - * in a table packed into three ints). If too large, this is - * retried after rotating the hash by nbits bits, while forcing new - * top bit to 0, which guarantees eventual termination (although - * with a non-random-bias). This requires an average of less than - * 2 tries for all table sizes, and has a maximum 2% difference - * from perfectly uniform slot probabilities when applied to all - * possible hash codes for sizes less than 32. - * - * @return a per-thread-random index, 0 <= index < max - */ - private final int hashIndex() { - long id = Thread.currentThread().getId(); - int hash = (((int)(id ^ (id >>> 32))) ^ 0x811c9dc5) * 0x01000193; - - int m = max.get(); - int nbits = (((0xfffffc00 >> m) & 4) | // Compute ceil(log2(m+1)) - ((0x000001f8 >>> m) & 2) | // The constants hold - ((0xffff00f2 >>> m) & 1)); // a lookup table - int index; - while ((index = hash & ((1 << nbits) - 1)) > m) // May retry on - hash = (hash >>> nbits) | (hash << (33 - nbits)); // non-power-2 m - return index; + /** The corresponding thread local class */ + static final class Participant extends ThreadLocal { + public Node initialValue() { return new Node(); } } /** - * Creates a new slot at given index. Called only when the slot - * appears to be null. Relies on double-check using builtin - * locks, since they rarely contend. This in turn relies on the - * arena array being declared volatile. - * - * @param index the index to add slot at + * Per-thread state */ - private void createSlot(int index) { - // Create slot outside of lock to narrow sync region - Slot newSlot = new Slot(); - Slot[] a = arena; - synchronized (a) { - if (a[index] == null) - a[index] = newSlot; - } - } + private final Participant participant; /** - * Tries to cancel a wait for the given node waiting in the given - * slot, if so, helping clear the node from its slot to avoid - * garbage retention. - * - * @param node the waiting node - * @param the slot it is waiting in - * @return true if successfully cancelled + * Elimination array; null until enabled (within slotExchange). + * Element accesses use emulation of volatile gets and CAS. */ - private static boolean tryCancel(Node node, Slot slot) { - if (!node.compareAndSet(null, CANCEL)) - return false; - if (slot.get() == node) // pre-check to minimize contention - slot.compareAndSet(node, null); - return true; - } - - // Three forms of waiting. Each just different enough not to merge - // code with others. + private volatile Node[] arena; /** - * Spin-waits for hole for a non-0 slot. Fails if spin elapses - * before hole filled. Does not check interrupt, relying on check - * in public exchange method to abort if interrupted on entry. - * - * @param node the waiting node - * @return on success, the hole; on failure, CANCEL + * Slot used until contention detected. */ - private static Object spinWait(Node node, Slot slot) { - int spins = SPINS; - for (;;) { - Object v = node.get(); - if (v != null) - return v; - else if (spins > 0) - --spins; - else - tryCancel(node, slot); - } - } + private volatile Node slot; /** - * Waits for (by spinning and/or blocking) and gets the hole - * filled in by another thread. Fails if interrupted before - * hole filled. - * - * When a node/thread is about to block, it sets its waiter field - * and then rechecks state at least one more time before actually - * parking, thus covering race vs fulfiller noticing that waiter - * is non-null so should be woken. - * - * Thread interruption status is checked only surrounding calls to - * park. The caller is assumed to have checked interrupt status - * on entry. - * - * @param node the waiting node - * @return on success, the hole; on failure, CANCEL + * The index of the largest valid arena position, OR'ed with SEQ + * number in high bits, incremented on each update. The initial + * update from 0 to SEQ is used to ensure that the arena array is + * constructed only once. */ - private static Object await(Node node, Slot slot) { - Thread w = Thread.currentThread(); - int spins = SPINS; - for (;;) { - Object v = node.get(); - if (v != null) - return v; - else if (spins > 0) // Spin-wait phase - --spins; - else if (node.waiter == null) // Set up to block next - node.waiter = w; - else if (w.isInterrupted()) // Abort on interrupt - tryCancel(node, slot); - else // Block - LockSupport.park(node); - } - } + private volatile int bound; /** - * Waits for (at index 0) and gets the hole filled in by another - * thread. Fails if timed out or interrupted before hole filled. - * Same basic logic as untimed version, but a bit messier. + * Exchange function when arenas enabled. See above for explanation. * - * @param node the waiting node - * @param nanos the wait time - * @return on success, the hole; on failure, CANCEL + * @param item the (non-null) item to exchange + * @param timed true if the wait is timed + * @param ns if timed, the maximum wait time, else 0L + * @return the other thread's item; or null if interrupted; or + * TIMED_OUT if timed and timed out */ - private Object awaitNanos(Node node, Slot slot, long nanos) { - int spins = TIMED_SPINS; - long lastTime = 0; - Thread w = null; - for (;;) { - Object v = node.get(); - if (v != null) + private final Object arenaExchange(Object item, boolean timed, long ns) { + Node[] a = arena; + Node p = participant.get(); + for (int i = p.index;;) { // access slot at i + int b, m, c; long j; // j is raw array offset + Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE); + if (q != null && U.compareAndSwapObject(a, j, q, null)) { + Object v = q.item; // release + q.match = item; + Thread w = q.parked; + if (w != null) + U.unpark(w); return v; - long now = System.nanoTime(); - if (w == null) - w = Thread.currentThread(); - else - nanos -= now - lastTime; - lastTime = now; - if (nanos > 0) { - if (spins > 0) - --spins; - else if (node.waiter == null) - node.waiter = w; - else if (w.isInterrupted()) - tryCancel(node, slot); + } + else if (i <= (m = (b = bound) & MMASK) && q == null) { + p.item = item; // offer + if (U.compareAndSwapObject(a, j, null, p)) { + long end = (timed && m == 0) ? System.nanoTime() + ns : 0L; + Thread t = Thread.currentThread(); // wait + for (int h = p.hash, spins = SPINS;;) { + Object v = p.match; + if (v != null) { + U.putOrderedObject(p, MATCH, null); + p.item = null; // clear for next use + p.hash = h; + return v; + } + else if (spins > 0) { + h ^= h << 1; h ^= h >>> 3; h ^= h << 10; // xorshift + if (h == 0) // initialize hash + h = SPINS | (int)t.getId(); + else if (h < 0 && // approx 50% true + (--spins & ((SPINS >>> 1) - 1)) == 0) + Thread.yield(); // two yields per wait + } + else if (U.getObjectVolatile(a, j) != p) + spins = SPINS; // releaser hasn't set match yet + else if (!t.isInterrupted() && m == 0 && + (!timed || + (ns = end - System.nanoTime()) > 0L)) { + U.putObject(t, BLOCKER, this); // emulate LockSupport + p.parked = t; // minimize window + if (U.getObjectVolatile(a, j) == p) + U.park(false, ns); + p.parked = null; + U.putObject(t, BLOCKER, null); + } + else if (U.getObjectVolatile(a, j) == p && + U.compareAndSwapObject(a, j, p, null)) { + if (m != 0) // try to shrink + U.compareAndSwapInt(this, BOUND, b, b + SEQ - 1); + p.item = null; + p.hash = h; + i = p.index >>>= 1; // descend + if (Thread.interrupted()) + return null; + if (timed && m == 0 && ns <= 0L) + return TIMED_OUT; + break; // expired; restart + } + } + } else - LockSupport.parkNanos(node, nanos); + p.item = null; // clear offer + } + else { + if (p.bound != b) { // stale; reset + p.bound = b; + p.collides = 0; + i = (i != m || m == 0) ? m : m - 1; + } + else if ((c = p.collides) < m || m == FULL || + !U.compareAndSwapInt(this, BOUND, b, b + SEQ + 1)) { + p.collides = c + 1; + i = (i == 0) ? m : i - 1; // cyclically traverse + } + else + i = m + 1; // grow + p.index = i; } - else if (tryCancel(node, slot) && !w.isInterrupted()) - return scanOnTimeout(node); } } /** - * Sweeps through arena checking for any waiting threads. Called - * only upon return from timeout while waiting in slot 0. When a - * thread gives up on a timed wait, it is possible that a - * previously-entered thread is still waiting in some other - * slot. So we scan to check for any. This is almost always - * overkill, but decreases the likelihood of timeouts when there - * are other threads present to far less than that in lock-based - * exchangers in which earlier-arriving threads may still be - * waiting on entry locks. + * Exchange function used until arenas enabled. See above for explanation. * - * @param node the waiting node - * @return another thread's item, or CANCEL + * @param item the item to exchange + * @param timed true if the wait is timed + * @param ns if timed, the maximum wait time, else 0L + * @return the other thread's item; or null if either the arena + * was enabled or the thread was interrupted before completion; or + * TIMED_OUT if timed and timed out */ - private Object scanOnTimeout(Node node) { - Object y; - for (int j = arena.length - 1; j >= 0; --j) { - Slot slot = arena[j]; - if (slot != null) { - while ((y = slot.get()) != null) { - if (slot.compareAndSet(y, null)) { - Node you = (Node)y; - if (you.compareAndSet(null, node.item)) { - LockSupport.unpark(you.waiter); - return you.item; - } - } + private final Object slotExchange(Object item, boolean timed, long ns) { + Node p = participant.get(); + Thread t = Thread.currentThread(); + if (t.isInterrupted()) // preserve interrupt status so caller can recheck + return null; + + for (Node q;;) { + if ((q = slot) != null) { + if (U.compareAndSwapObject(this, SLOT, q, null)) { + Object v = q.item; + q.match = item; + Thread w = q.parked; + if (w != null) + U.unpark(w); + return v; } + // create arena on contention, but continue until slot null + if (NCPU > 1 && bound == 0 && + U.compareAndSwapInt(this, BOUND, 0, SEQ)) + arena = new Node[(FULL + 2) << ASHIFT]; + } + else if (arena != null) + return null; // caller must reroute to arenaExchange + else { + p.item = item; + if (U.compareAndSwapObject(this, SLOT, null, p)) + break; + p.item = null; } } - return CANCEL; + + // await release + int h = p.hash; + long end = timed ? System.nanoTime() + ns : 0L; + int spins = (NCPU > 1) ? SPINS : 1; + Object v; + while ((v = p.match) == null) { + if (spins > 0) { + h ^= h << 1; h ^= h >>> 3; h ^= h << 10; + if (h == 0) + h = SPINS | (int)t.getId(); + else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0) + Thread.yield(); + } + else if (slot != p) + spins = SPINS; + else if (!t.isInterrupted() && arena == null && + (!timed || (ns = end - System.nanoTime()) > 0L)) { + U.putObject(t, BLOCKER, this); + p.parked = t; + if (slot == p) + U.park(false, ns); + p.parked = null; + U.putObject(t, BLOCKER, null); + } + else if (U.compareAndSwapObject(this, SLOT, p, null)) { + v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null; + break; + } + } + U.putOrderedObject(p, MATCH, null); + p.item = null; + p.hash = h; + return v; } /** * Creates a new Exchanger. */ public Exchanger() { + participant = new Participant(); } /** @@ -620,15 +559,14 @@ public class Exchanger { */ @SuppressWarnings("unchecked") public V exchange(V x) throws InterruptedException { - if (!Thread.interrupted()) { - Object o = doExchange((x == null) ? NULL_ITEM : x, false, 0); - if (o == NULL_ITEM) - return null; - if (o != CANCEL) - return (V)o; - Thread.interrupted(); // Clear interrupt status on IE throw - } - throw new InterruptedException(); + Object v; + Object item = (x == null) ? NULL_ITEM : x; // translate null args + if ((arena != null || + (v = slotExchange(item, false, 0L)) == null) && + ((Thread.interrupted() || // disambiguates null return + (v = arenaExchange(item, false, 0L)) == null))) + throw new InterruptedException(); + return (v == NULL_ITEM) ? null : (V)v; } /** @@ -666,7 +604,7 @@ public class Exchanger { * * @param x the object to exchange * @param timeout the maximum time to wait - * @param unit the time unit of the timeout argument + * @param unit the time unit of the {@code timeout} argument * @return the object provided by the other thread * @throws InterruptedException if the current thread was * interrupted while waiting @@ -676,16 +614,51 @@ public class Exchanger { @SuppressWarnings("unchecked") public V exchange(V x, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { - if (!Thread.interrupted()) { - Object o = doExchange((x == null) ? NULL_ITEM : x, - true, unit.toNanos(timeout)); - if (o == NULL_ITEM) - return null; - if (o != CANCEL) - return (V)o; - if (!Thread.interrupted()) - throw new TimeoutException(); + Object v; + Object item = (x == null) ? NULL_ITEM : x; + long ns = unit.toNanos(timeout); + if ((arena != null || + (v = slotExchange(item, true, ns)) == null) && + ((Thread.interrupted() || + (v = arenaExchange(item, true, ns)) == null))) + throw new InterruptedException(); + if (v == TIMED_OUT) + throw new TimeoutException(); + return (v == NULL_ITEM) ? null : (V)v; + } + + // Unsafe mechanics + private static final sun.misc.Unsafe U; + private static final long BOUND; + private static final long SLOT; + private static final long MATCH; + private static final long BLOCKER; + private static final int ABASE; + static { + int s; + try { + U = sun.misc.Unsafe.getUnsafe(); + Class ek = Exchanger.class; + Class nk = Node.class; + Class ak = Node[].class; + Class tk = Thread.class; + BOUND = U.objectFieldOffset + (ek.getDeclaredField("bound")); + SLOT = U.objectFieldOffset + (ek.getDeclaredField("slot")); + MATCH = U.objectFieldOffset + (nk.getDeclaredField("match")); + BLOCKER = U.objectFieldOffset + (tk.getDeclaredField("parkBlocker")); + s = U.arrayIndexScale(ak); + // ABASE absorbs padding in front of element 0 + ABASE = U.arrayBaseOffset(ak) + (1 << ASHIFT); + + } catch (Exception e) { + throw new Error(e); } - throw new InterruptedException(); + if ((s & (s-1)) != 0 || s > (1 << ASHIFT)) + throw new Error("Unsupported array scale"); } + } diff --git a/src/share/classes/java/util/concurrent/LinkedBlockingDeque.java b/src/share/classes/java/util/concurrent/LinkedBlockingDeque.java index b79bc9444e35a80203ec83e4d3bf932302a061db..df688a57b066d4f245ce263fc36e9e7eff833ece 100644 --- a/src/share/classes/java/util/concurrent/LinkedBlockingDeque.java +++ b/src/share/classes/java/util/concurrent/LinkedBlockingDeque.java @@ -41,12 +41,15 @@ import java.util.Iterator; import java.util.NoSuchElementException; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; /** * An optionally-bounded {@linkplain BlockingDeque blocking deque} based on * linked nodes. * - *

The optional capacity bound constructor argument serves as a + *

The optional capacity bound constructor argument serves as a * way to prevent excessive expansion. The capacity, if unspecified, * is equal to {@link Integer#MAX_VALUE}. Linked nodes are * dynamically created upon each insertion unless this would bring the @@ -315,8 +318,8 @@ public class LinkedBlockingDeque // BlockingDeque methods /** - * @throws IllegalStateException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} + * @throws IllegalStateException if this deque is full + * @throws NullPointerException {@inheritDoc} */ public void addFirst(E e) { if (!offerFirst(e)) @@ -324,7 +327,7 @@ public class LinkedBlockingDeque } /** - * @throws IllegalStateException {@inheritDoc} + * @throws IllegalStateException if this deque is full * @throws NullPointerException {@inheritDoc} */ public void addLast(E e) { @@ -623,8 +626,7 @@ public class LinkedBlockingDeque * *

This method is equivalent to {@link #addLast}. * - * @throws IllegalStateException if the element cannot be added at this - * time due to capacity restrictions + * @throws IllegalStateException if this deque is full * @throws NullPointerException if the specified element is null */ public boolean add(E e) { @@ -761,8 +763,8 @@ public class LinkedBlockingDeque // Stack methods /** - * @throws IllegalStateException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} + * @throws IllegalStateException if this deque is full + * @throws NullPointerException {@inheritDoc} */ public void push(E e) { addFirst(e); @@ -852,7 +854,7 @@ public class LinkedBlockingDeque // * @throws ClassCastException {@inheritDoc} // * @throws NullPointerException {@inheritDoc} // * @throws IllegalArgumentException {@inheritDoc} -// * @throws IllegalStateException {@inheritDoc} +// * @throws IllegalStateException if this deque is full // * @see #add(Object) // */ // public boolean addAll(Collection c) { @@ -1151,6 +1153,127 @@ public class LinkedBlockingDeque Node nextNode(Node n) { return n.prev; } } + /** A customized variant of Spliterators.IteratorSpliterator */ + static final class LBDSpliterator implements Spliterator { + static final int MAX_BATCH = 1 << 25; // max batch array size; + final LinkedBlockingDeque queue; + Node current; // current node; null until initialized + int batch; // batch size for splits + boolean exhausted; // true when no more nodes + long est; // size estimate + LBDSpliterator(LinkedBlockingDeque queue) { + this.queue = queue; + this.est = queue.size(); + } + + public long estimateSize() { return est; } + + public Spliterator trySplit() { + Node h; + final LinkedBlockingDeque q = this.queue; + int b = batch; + int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; + if (!exhausted && + ((h = current) != null || (h = q.first) != null) && + h.next != null) { + Object[] a = new Object[n]; + final ReentrantLock lock = q.lock; + int i = 0; + Node p = current; + lock.lock(); + try { + if (p != null || (p = q.first) != null) { + do { + if ((a[i] = p.item) != null) + ++i; + } while ((p = p.next) != null && i < n); + } + } finally { + lock.unlock(); + } + if ((current = p) == null) { + est = 0L; + exhausted = true; + } + else if ((est -= i) < 0L) + est = 0L; + if (i > 0) { + batch = i; + return Spliterators.spliterator + (a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT); + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + if (action == null) throw new NullPointerException(); + final LinkedBlockingDeque q = this.queue; + final ReentrantLock lock = q.lock; + if (!exhausted) { + exhausted = true; + Node p = current; + do { + E e = null; + lock.lock(); + try { + if (p == null) + p = q.first; + while (p != null) { + e = p.item; + p = p.next; + if (e != null) + break; + } + } finally { + lock.unlock(); + } + if (e != null) + action.accept(e); + } while (p != null); + } + } + + public boolean tryAdvance(Consumer action) { + if (action == null) throw new NullPointerException(); + final LinkedBlockingDeque q = this.queue; + final ReentrantLock lock = q.lock; + if (!exhausted) { + E e = null; + lock.lock(); + try { + if (current == null) + current = q.first; + while (current != null) { + e = current.item; + current = current.next; + if (e != null) + break; + } + } finally { + lock.unlock(); + } + if (current == null) + exhausted = true; + if (e != null) { + action.accept(e); + return true; + } + } + return false; + } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT; + } + } + + public Spliterator spliterator() { + return new LBDSpliterator(this); + } + /** * Saves this deque to a stream (that is, serializes it). * diff --git a/src/share/classes/java/util/concurrent/LinkedBlockingQueue.java b/src/share/classes/java/util/concurrent/LinkedBlockingQueue.java index e95e0a32af8c4fabcf0b9e24592f5ef8496bb325..05bf7cc22de759403bb30186212e991c045e71c1 100644 --- a/src/share/classes/java/util/concurrent/LinkedBlockingQueue.java +++ b/src/share/classes/java/util/concurrent/LinkedBlockingQueue.java @@ -42,6 +42,9 @@ import java.util.AbstractQueue; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; /** * An optionally-bounded {@linkplain BlockingQueue blocking queue} based on @@ -56,7 +59,7 @@ import java.util.NoSuchElementException; * Linked queues typically have higher throughput than array-based queues but * less predictable performance in most concurrent applications. * - *

The optional capacity bound constructor argument serves as a + *

The optional capacity bound constructor argument serves as a * way to prevent excessive queue expansion. The capacity, if unspecified, * is equal to {@link Integer#MAX_VALUE}. Linked nodes are * dynamically created upon each insertion unless this would bring the @@ -216,7 +219,7 @@ public class LinkedBlockingQueue extends AbstractQueue } /** - * Lock to prevent both puts and takes. + * Locks to prevent both puts and takes. */ void fullyLock() { putLock.lock(); @@ -224,7 +227,7 @@ public class LinkedBlockingQueue extends AbstractQueue } /** - * Unlock to allow both puts and takes. + * Unlocks to allow both puts and takes. */ void fullyUnlock() { takeLock.unlock(); @@ -362,7 +365,7 @@ public class LinkedBlockingQueue extends AbstractQueue * necessary up to the specified wait time for space to become available. * * @return {@code true} if successful, or {@code false} if - * the specified waiting time elapses before space is available. + * the specified waiting time elapses before space is available * @throws InterruptedException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @@ -782,6 +785,7 @@ public class LinkedBlockingQueue extends AbstractQueue * item to hand out so that if hasNext() reports true, we will * still have it to return even if lost race with a take etc. */ + private Node current; private Node lastRet; private E currentElement; @@ -855,6 +859,124 @@ public class LinkedBlockingQueue extends AbstractQueue } } + /** A customized variant of Spliterators.IteratorSpliterator */ + static final class LBQSpliterator implements Spliterator { + static final int MAX_BATCH = 1 << 25; // max batch array size; + final LinkedBlockingQueue queue; + Node current; // current node; null until initialized + int batch; // batch size for splits + boolean exhausted; // true when no more nodes + long est; // size estimate + LBQSpliterator(LinkedBlockingQueue queue) { + this.queue = queue; + this.est = queue.size(); + } + + public long estimateSize() { return est; } + + public Spliterator trySplit() { + Node h; + final LinkedBlockingQueue q = this.queue; + int b = batch; + int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; + if (!exhausted && + ((h = current) != null || (h = q.head.next) != null) && + h.next != null) { + Object[] a = new Object[n]; + int i = 0; + Node p = current; + q.fullyLock(); + try { + if (p != null || (p = q.head.next) != null) { + do { + if ((a[i] = p.item) != null) + ++i; + } while ((p = p.next) != null && i < n); + } + } finally { + q.fullyUnlock(); + } + if ((current = p) == null) { + est = 0L; + exhausted = true; + } + else if ((est -= i) < 0L) + est = 0L; + if (i > 0) { + batch = i; + return Spliterators.spliterator + (a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT); + } + } + return null; + } + + public void forEachRemaining(Consumer action) { + if (action == null) throw new NullPointerException(); + final LinkedBlockingQueue q = this.queue; + if (!exhausted) { + exhausted = true; + Node p = current; + do { + E e = null; + q.fullyLock(); + try { + if (p == null) + p = q.head.next; + while (p != null) { + e = p.item; + p = p.next; + if (e != null) + break; + } + } finally { + q.fullyUnlock(); + } + if (e != null) + action.accept(e); + } while (p != null); + } + } + + public boolean tryAdvance(Consumer action) { + if (action == null) throw new NullPointerException(); + final LinkedBlockingQueue q = this.queue; + if (!exhausted) { + E e = null; + q.fullyLock(); + try { + if (current == null) + current = q.head.next; + while (current != null) { + e = current.item; + current = current.next; + if (e != null) + break; + } + } finally { + q.fullyUnlock(); + } + if (current == null) + exhausted = true; + if (e != null) { + action.accept(e); + return true; + } + } + return false; + } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT; + } + } + + public Spliterator spliterator() { + return new LBQSpliterator(this); + } + /** * Saves this queue to a stream (that is, serializes it). * diff --git a/src/share/classes/java/util/concurrent/LinkedTransferQueue.java b/src/share/classes/java/util/concurrent/LinkedTransferQueue.java index 6b6e46a9264cab5215314dfdb2633ade10299e61..7dbb368cc52f3c9d67efe03c3b4849570f809bea 100644 --- a/src/share/classes/java/util/concurrent/LinkedTransferQueue.java +++ b/src/share/classes/java/util/concurrent/LinkedTransferQueue.java @@ -40,8 +40,10 @@ import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; /** * An unbounded {@link TransferQueue} based on linked nodes. @@ -776,6 +778,24 @@ public class LinkedTransferQueue extends AbstractQueue return null; } + /** + * Version of firstOfMode used by Spliterator + */ + final Node firstDataNode() { + for (Node p = head; p != null;) { + Object item = p.item; + if (p.isData) { + if (item != null && item != p) + return p; + } + else if (item == null) + break; + if (p == (p = p.next)) + p = head; + } + return null; + } + /** * Returns the item in the first unmatched node with isData; or * null if none. Used by peek. @@ -910,6 +930,98 @@ public class LinkedTransferQueue extends AbstractQueue } } + /** A customized variant of Spliterators.IteratorSpliterator */ + static final class LTQSpliterator implements Spliterator { + static final int MAX_BATCH = 1 << 25; // max batch array size; + final LinkedTransferQueue queue; + Node current; // current node; null until initialized + int batch; // batch size for splits + boolean exhausted; // true when no more nodes + LTQSpliterator(LinkedTransferQueue queue) { + this.queue = queue; + } + + public Spliterator trySplit() { + Node p; + final LinkedTransferQueue q = this.queue; + int b = batch; + int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; + if (!exhausted && + ((p = current) != null || (p = q.firstDataNode()) != null) && + p.next != null) { + Object[] a = new Object[n]; + int i = 0; + do { + if ((a[i] = p.item) != null) + ++i; + if (p == (p = p.next)) + p = q.firstDataNode(); + } while (p != null && i < n); + if ((current = p) == null) + exhausted = true; + if (i > 0) { + batch = i; + return Spliterators.spliterator + (a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT); + } + } + return null; + } + + @SuppressWarnings("unchecked") + public void forEachRemaining(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + final LinkedTransferQueue q = this.queue; + if (!exhausted && + ((p = current) != null || (p = q.firstDataNode()) != null)) { + exhausted = true; + do { + Object e = p.item; + if (p == (p = p.next)) + p = q.firstDataNode(); + if (e != null) + action.accept((E)e); + } while (p != null); + } + } + + @SuppressWarnings("unchecked") + public boolean tryAdvance(Consumer action) { + Node p; + if (action == null) throw new NullPointerException(); + final LinkedTransferQueue q = this.queue; + if (!exhausted && + ((p = current) != null || (p = q.firstDataNode()) != null)) { + Object e; + do { + e = p.item; + if (p == (p = p.next)) + p = q.firstDataNode(); + } while (e == null && p != null); + if ((current = p) == null) + exhausted = true; + if (e != null) { + action.accept((E)e); + return true; + } + } + return false; + } + + public long estimateSize() { return Long.MAX_VALUE; } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.NONNULL | + Spliterator.CONCURRENT; + } + } + + public Spliterator spliterator() { + return new LTQSpliterator(this); + } + /* -------------- Removal methods -------------- */ /** diff --git a/src/share/classes/java/util/concurrent/Phaser.java b/src/share/classes/java/util/concurrent/Phaser.java index c8afecc3c00ef5ef82fae7382c44f9ab84deacda..394f62bccf3b8b3d98f443c4be0eea23a334bb8e 100644 --- a/src/share/classes/java/util/concurrent/Phaser.java +++ b/src/share/classes/java/util/concurrent/Phaser.java @@ -46,7 +46,7 @@ import java.util.concurrent.locks.LockSupport; * {@link java.util.concurrent.CountDownLatch CountDownLatch} * but supporting more flexible usage. * - *

Registration. Unlike the case for other barriers, the + *

Registration. Unlike the case for other barriers, the * number of parties registered to synchronize on a phaser * may vary over time. Tasks may be registered at any time (using * methods {@link #register}, {@link #bulkRegister}, or forms of @@ -59,7 +59,7 @@ import java.util.concurrent.locks.LockSupport; * (However, you can introduce such bookkeeping by subclassing this * class.) * - *

Synchronization. Like a {@code CyclicBarrier}, a {@code + *

Synchronization. Like a {@code CyclicBarrier}, a {@code * Phaser} may be repeatedly awaited. Method {@link * #arriveAndAwaitAdvance} has effect analogous to {@link * java.util.concurrent.CyclicBarrier#await CyclicBarrier.await}. Each @@ -103,7 +103,7 @@ import java.util.concurrent.locks.LockSupport; * * * - *

Termination. A phaser may enter a termination + *

Termination. A phaser may enter a termination * state, that may be checked using method {@link #isTerminated}. Upon * termination, all synchronization methods immediately return without * waiting for advance, as indicated by a negative return value. @@ -118,7 +118,7 @@ import java.util.concurrent.locks.LockSupport; * also available to abruptly release waiting threads and allow them * to terminate. * - *

Tiering. Phasers may be tiered (i.e., + *

Tiering. Phasers may be tiered (i.e., * constructed in tree structures) to reduce contention. Phasers with * large numbers of parties that would otherwise experience heavy * synchronization contention costs may instead be set up so that @@ -300,18 +300,20 @@ public class Phaser { private static final int PHASE_SHIFT = 32; private static final int UNARRIVED_MASK = 0xffff; // to mask ints private static final long PARTIES_MASK = 0xffff0000L; // to mask longs + private static final long COUNTS_MASK = 0xffffffffL; private static final long TERMINATION_BIT = 1L << 63; // some special values private static final int ONE_ARRIVAL = 1; private static final int ONE_PARTY = 1 << PARTIES_SHIFT; + private static final int ONE_DEREGISTER = ONE_ARRIVAL|ONE_PARTY; private static final int EMPTY = 1; // The following unpacking methods are usually manually inlined private static int unarrivedOf(long s) { int counts = (int)s; - return (counts == EMPTY) ? 0 : counts & UNARRIVED_MASK; + return (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK); } private static int partiesOf(long s) { @@ -372,37 +374,44 @@ public class Phaser { * Manually tuned to speed up and minimize race windows for the * common case of just decrementing unarrived field. * - * @param deregister false for arrive, true for arriveAndDeregister + * @param adjust value to subtract from state; + * ONE_ARRIVAL for arrive, + * ONE_DEREGISTER for arriveAndDeregister */ - private int doArrive(boolean deregister) { - int adj = deregister ? ONE_ARRIVAL|ONE_PARTY : ONE_ARRIVAL; + private int doArrive(int adjust) { final Phaser root = this.root; for (;;) { long s = (root == this) ? state : reconcileState(); int phase = (int)(s >>> PHASE_SHIFT); - int counts = (int)s; - int unarrived = (counts & UNARRIVED_MASK) - 1; if (phase < 0) return phase; - else if (counts == EMPTY || unarrived < 0) { - if (root == this || reconcileState() == s) - throw new IllegalStateException(badArrive(s)); - } - else if (UNSAFE.compareAndSwapLong(this, stateOffset, s, s-=adj)) { - if (unarrived == 0) { + int counts = (int)s; + int unarrived = (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK); + if (unarrived <= 0) + throw new IllegalStateException(badArrive(s)); + if (UNSAFE.compareAndSwapLong(this, stateOffset, s, s-=adjust)) { + if (unarrived == 1) { long n = s & PARTIES_MASK; // base of next state int nextUnarrived = (int)n >>> PARTIES_SHIFT; - if (root != this) - return parent.doArrive(nextUnarrived == 0); - if (onAdvance(phase, nextUnarrived)) - n |= TERMINATION_BIT; - else if (nextUnarrived == 0) - n |= EMPTY; + if (root == this) { + if (onAdvance(phase, nextUnarrived)) + n |= TERMINATION_BIT; + else if (nextUnarrived == 0) + n |= EMPTY; + else + n |= nextUnarrived; + int nextPhase = (phase + 1) & MAX_PHASE; + n |= (long)nextPhase << PHASE_SHIFT; + UNSAFE.compareAndSwapLong(this, stateOffset, s, n); + releaseWaiters(phase); + } + else if (nextUnarrived == 0) { // propagate deregistration + phase = parent.doArrive(ONE_DEREGISTER); + UNSAFE.compareAndSwapLong(this, stateOffset, + s, s | EMPTY); + } else - n |= nextUnarrived; - n |= (long)((phase + 1) & MAX_PHASE) << PHASE_SHIFT; - UNSAFE.compareAndSwapLong(this, stateOffset, s, n); - releaseWaiters(phase); + phase = parent.doArrive(ONE_ARRIVAL); } return phase; } @@ -417,42 +426,49 @@ public class Phaser { */ private int doRegister(int registrations) { // adjustment to state - long adj = ((long)registrations << PARTIES_SHIFT) | registrations; + long adjust = ((long)registrations << PARTIES_SHIFT) | registrations; final Phaser parent = this.parent; int phase; for (;;) { - long s = state; + long s = (parent == null) ? state : reconcileState(); int counts = (int)s; int parties = counts >>> PARTIES_SHIFT; int unarrived = counts & UNARRIVED_MASK; if (registrations > MAX_PARTIES - parties) throw new IllegalStateException(badRegister(s)); - else if ((phase = (int)(s >>> PHASE_SHIFT)) < 0) + phase = (int)(s >>> PHASE_SHIFT); + if (phase < 0) break; - else if (counts != EMPTY) { // not 1st registration + if (counts != EMPTY) { // not 1st registration if (parent == null || reconcileState() == s) { if (unarrived == 0) // wait out advance root.internalAwaitAdvance(phase, null); else if (UNSAFE.compareAndSwapLong(this, stateOffset, - s, s + adj)) + s, s + adjust)) break; } } else if (parent == null) { // 1st root registration - long next = ((long)phase << PHASE_SHIFT) | adj; + long next = ((long)phase << PHASE_SHIFT) | adjust; if (UNSAFE.compareAndSwapLong(this, stateOffset, s, next)) break; } else { synchronized (this) { // 1st sub registration if (state == s) { // recheck under lock - parent.doRegister(1); - do { // force current phase + phase = parent.doRegister(1); + if (phase < 0) + break; + // finish registration whenever parent registration + // succeeded, even when racing with termination, + // since these are part of the same "transaction". + while (!UNSAFE.compareAndSwapLong + (this, stateOffset, s, + ((long)phase << PHASE_SHIFT) | adjust)) { + s = state; phase = (int)(root.state >>> PHASE_SHIFT); - // assert phase < 0 || (int)state == EMPTY; - } while (!UNSAFE.compareAndSwapLong - (this, stateOffset, state, - ((long)phase << PHASE_SHIFT) | adj)); + // assert (int)s == EMPTY; + } break; } } @@ -467,10 +483,6 @@ public class Phaser { * subphasers have not yet done so, in which case they must finish * their own advance by setting unarrived to parties (or if * parties is zero, resetting to unregistered EMPTY state). - * However, this method may also be called when "floating" - * subphasers with possibly some unarrived parties are merely - * catching up to current phase, in which case counts are - * unaffected. * * @return reconciled state */ @@ -478,16 +490,16 @@ public class Phaser { final Phaser root = this.root; long s = state; if (root != this) { - int phase, u, p; - // CAS root phase with current parties; possibly trip unarrived + int phase, p; + // CAS to root phase with current parties, tripping unarrived while ((phase = (int)(root.state >>> PHASE_SHIFT)) != (int)(s >>> PHASE_SHIFT) && !UNSAFE.compareAndSwapLong (this, stateOffset, s, s = (((long)phase << PHASE_SHIFT) | - (s & PARTIES_MASK) | - ((p = (int)s >>> PARTIES_SHIFT) == 0 ? EMPTY : - (u = (int)s & UNARRIVED_MASK) == 0 ? p : u)))) + ((phase < 0) ? (s & COUNTS_MASK) : + (((p = (int)s >>> PARTIES_SHIFT) == 0) ? EMPTY : + ((s & PARTIES_MASK) | p)))))) s = state; } return s; @@ -619,7 +631,7 @@ public class Phaser { * of unarrived parties would become negative */ public int arrive() { - return doArrive(false); + return doArrive(ONE_ARRIVAL); } /** @@ -639,7 +651,7 @@ public class Phaser { * of registered or unarrived parties would become negative */ public int arriveAndDeregister() { - return doArrive(true); + return doArrive(ONE_DEREGISTER); } /** @@ -666,17 +678,15 @@ public class Phaser { for (;;) { long s = (root == this) ? state : reconcileState(); int phase = (int)(s >>> PHASE_SHIFT); - int counts = (int)s; - int unarrived = (counts & UNARRIVED_MASK) - 1; if (phase < 0) return phase; - else if (counts == EMPTY || unarrived < 0) { - if (reconcileState() == s) - throw new IllegalStateException(badArrive(s)); - } - else if (UNSAFE.compareAndSwapLong(this, stateOffset, s, - s -= ONE_ARRIVAL)) { - if (unarrived != 0) + int counts = (int)s; + int unarrived = (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK); + if (unarrived <= 0) + throw new IllegalStateException(badArrive(s)); + if (UNSAFE.compareAndSwapLong(this, stateOffset, s, + s -= ONE_ARRIVAL)) { + if (unarrived > 1) return root.internalAwaitAdvance(phase, null); if (root != this) return parent.arriveAndAwaitAdvance(); @@ -809,8 +819,8 @@ public class Phaser { if (UNSAFE.compareAndSwapLong(root, stateOffset, s, s | TERMINATION_BIT)) { // signal all threads - releaseWaiters(0); - releaseWaiters(1); + releaseWaiters(0); // Waiters on evenQ + releaseWaiters(1); // Waiters on oddQ return; } } @@ -1016,7 +1026,7 @@ public class Phaser { /** * Possibly blocks and waits for phase to advance unless aborted. - * Call only from root node. + * Call only on root phaser. * * @param phase current phase * @param node if non-null, the wait node to track interrupt and timeout; @@ -1024,6 +1034,7 @@ public class Phaser { * @return current phase */ private int internalAwaitAdvance(int phase, QNode node) { + // assert root == this; releaseWaiters(phase-1); // ensure old queue clean boolean queued = false; // true when node is enqueued int lastUnarrived = 0; // to increase spins upon change @@ -1082,7 +1093,7 @@ public class Phaser { final boolean timed; boolean wasInterrupted; long nanos; - long lastTime; + final long deadline; volatile Thread thread; // nulled to cancel wait QNode next; @@ -1093,7 +1104,7 @@ public class Phaser { this.interruptible = interruptible; this.nanos = nanos; this.timed = timed; - this.lastTime = timed ? System.nanoTime() : 0L; + this.deadline = timed ? System.nanoTime() + nanos : 0L; thread = Thread.currentThread(); } @@ -1112,9 +1123,7 @@ public class Phaser { } if (timed) { if (nanos > 0L) { - long now = System.nanoTime(); - nanos -= now - lastTime; - lastTime = now; + nanos = deadline - System.nanoTime(); } if (nanos <= 0L) { thread = null; @@ -1129,7 +1138,7 @@ public class Phaser { return true; else if (!timed) LockSupport.park(this); - else if (nanos > 0) + else if (nanos > 0L) LockSupport.parkNanos(this, nanos); return isReleasable(); } diff --git a/src/share/classes/java/util/concurrent/PriorityBlockingQueue.java b/src/share/classes/java/util/concurrent/PriorityBlockingQueue.java index d78cf205525ef8d05e1dc9a7a4124208cf805bf8..fdf9c71af9315e7d99255fe6c0009af3052ba85d 100644 --- a/src/share/classes/java/util/concurrent/PriorityBlockingQueue.java +++ b/src/share/classes/java/util/concurrent/PriorityBlockingQueue.java @@ -37,7 +37,17 @@ package java.util.concurrent; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; -import java.util.*; +import java.util.AbstractQueue; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.function.Consumer; /** * An unbounded {@linkplain BlockingQueue blocking queue} that uses @@ -342,7 +352,6 @@ public class PriorityBlockingQueue extends AbstractQueue * @param k the position to fill * @param x the item to insert * @param array the heap array - * @param n heap size */ private static void siftUpComparable(int k, T x, Object[] array) { Comparable key = (Comparable) x; @@ -936,6 +945,70 @@ public class PriorityBlockingQueue extends AbstractQueue } } + // Similar to Collections.ArraySnapshotSpliterator but avoids + // commitment to toArray until needed + static final class PBQSpliterator implements Spliterator { + final PriorityBlockingQueue queue; + Object[] array; + int index; + int fence; + + PBQSpliterator(PriorityBlockingQueue queue, Object[] array, + int index, int fence) { + this.queue = queue; + this.array = array; + this.index = index; + this.fence = fence; + } + + final int getFence() { + int hi; + if ((hi = fence) < 0) + hi = fence = (array = queue.toArray()).length; + return hi; + } + + public Spliterator trySplit() { + int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; + return (lo >= mid) ? null : + new PBQSpliterator(queue, array, lo, index = mid); + } + + @SuppressWarnings("unchecked") + public void forEachRemaining(Consumer action) { + Object[] a; int i, hi; // hoist accesses and checks from loop + if (action == null) + throw new NullPointerException(); + if ((a = array) == null) + fence = (a = queue.toArray()).length; + if ((hi = fence) <= a.length && + (i = index) >= 0 && i < (index = hi)) { + do { action.accept((E)a[i]); } while (++i < hi); + } + } + + public boolean tryAdvance(Consumer action) { + if (action == null) + throw new NullPointerException(); + if (getFence() > index && index >= 0) { + @SuppressWarnings("unchecked") E e = (E) array[index++]; + action.accept(e); + return true; + } + return false; + } + + public long estimateSize() { return (long)(getFence() - index); } + + public int characteristics() { + return Spliterator.NONNULL | Spliterator.SIZED | Spliterator.SUBSIZED; + } + } + + public Spliterator spliterator() { + return new PBQSpliterator(this, null, 0, -1); + } + // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long allocationSpinLockOffset; diff --git a/src/share/classes/java/util/concurrent/SynchronousQueue.java b/src/share/classes/java/util/concurrent/SynchronousQueue.java index ee9b121885457a7348ff4ce3f26d4bc7189acaba..97b53049716bcec89dd1aa5394405ce1cddaa3fb 100644 --- a/src/share/classes/java/util/concurrent/SynchronousQueue.java +++ b/src/share/classes/java/util/concurrent/SynchronousQueue.java @@ -44,17 +44,17 @@ import java.util.*; * operation must wait for a corresponding remove operation by another * thread, and vice versa. A synchronous queue does not have any * internal capacity, not even a capacity of one. You cannot - * peek at a synchronous queue because an element is only + * {@code peek} at a synchronous queue because an element is only * present when you try to remove it; you cannot insert an element * (using any method) unless another thread is trying to remove it; * you cannot iterate as there is nothing to iterate. The * head of the queue is the element that the first queued * inserting thread is trying to add to the queue; if there is no such * queued thread then no element is available for removal and - * poll() will return null. For purposes of other - * Collection methods (for example contains), a - * SynchronousQueue acts as an empty collection. This queue - * does not permit null elements. + * {@code poll()} will return {@code null}. For purposes of other + * {@code Collection} methods (for example {@code contains}), a + * {@code SynchronousQueue} acts as an empty collection. This queue + * does not permit {@code null} elements. * *

Synchronous queues are similar to rendezvous channels used in * CSP and Ada. They are well suited for handoff designs, in which an @@ -62,10 +62,10 @@ import java.util.*; * in another thread in order to hand it some information, event, or * task. * - *

This class supports an optional fairness policy for ordering + *

This class supports an optional fairness policy for ordering * waiting producer and consumer threads. By default, this ordering * is not guaranteed. However, a queue constructed with fairness set - * to true grants threads access in FIFO order. + * to {@code true} grants threads access in FIFO order. * *

This class and its iterator implement all of the * optional methods of the {@link Collection} and {@link @@ -599,7 +599,7 @@ public class SynchronousQueue extends AbstractQueue /** * Reference to a cancelled node that might not yet have been * unlinked from queue because it was the last inserted node - * when it cancelled. + * when it was cancelled. */ transient volatile QNode cleanMe; @@ -847,14 +847,14 @@ public class SynchronousQueue extends AbstractQueue private transient volatile Transferer transferer; /** - * Creates a SynchronousQueue with nonfair access policy. + * Creates a {@code SynchronousQueue} with nonfair access policy. */ public SynchronousQueue() { this(false); } /** - * Creates a SynchronousQueue with the specified fairness policy. + * Creates a {@code SynchronousQueue} with the specified fairness policy. * * @param fair if true, waiting threads contend in FIFO order for * access; otherwise the order is unspecified. @@ -882,8 +882,8 @@ public class SynchronousQueue extends AbstractQueue * Inserts the specified element into this queue, waiting if necessary * up to the specified wait time for another thread to receive it. * - * @return true if successful, or false if the - * specified waiting time elapses before a consumer appears. + * @return {@code true} if successful, or {@code false} if the + * specified waiting time elapses before a consumer appears * @throws InterruptedException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @@ -902,8 +902,8 @@ public class SynchronousQueue extends AbstractQueue * waiting to receive it. * * @param e the element to add - * @return true if the element was added to this queue, else - * false + * @return {@code true} if the element was added to this queue, else + * {@code false} * @throws NullPointerException if the specified element is null */ public boolean offer(E e) { @@ -931,8 +931,8 @@ public class SynchronousQueue extends AbstractQueue * if necessary up to the specified wait time, for another thread * to insert it. * - * @return the head of this queue, or null if the - * specified waiting time elapses before an element is present. + * @return the head of this queue, or {@code null} if the + * specified waiting time elapses before an element is present * @throws InterruptedException {@inheritDoc} */ public E poll(long timeout, TimeUnit unit) throws InterruptedException { @@ -946,18 +946,18 @@ public class SynchronousQueue extends AbstractQueue * Retrieves and removes the head of this queue, if another thread * is currently making an element available. * - * @return the head of this queue, or null if no - * element is available. + * @return the head of this queue, or {@code null} if no + * element is available */ public E poll() { return transferer.transfer(null, true, 0); } /** - * Always returns true. - * A SynchronousQueue has no internal capacity. + * Always returns {@code true}. + * A {@code SynchronousQueue} has no internal capacity. * - * @return true + * @return {@code true} */ public boolean isEmpty() { return true; @@ -965,9 +965,9 @@ public class SynchronousQueue extends AbstractQueue /** * Always returns zero. - * A SynchronousQueue has no internal capacity. + * A {@code SynchronousQueue} has no internal capacity. * - * @return zero. + * @return zero */ public int size() { return 0; @@ -975,9 +975,9 @@ public class SynchronousQueue extends AbstractQueue /** * Always returns zero. - * A SynchronousQueue has no internal capacity. + * A {@code SynchronousQueue} has no internal capacity. * - * @return zero. + * @return zero */ public int remainingCapacity() { return 0; @@ -985,80 +985,80 @@ public class SynchronousQueue extends AbstractQueue /** * Does nothing. - * A SynchronousQueue has no internal capacity. + * A {@code SynchronousQueue} has no internal capacity. */ public void clear() { } /** - * Always returns false. - * A SynchronousQueue has no internal capacity. + * Always returns {@code false}. + * A {@code SynchronousQueue} has no internal capacity. * * @param o the element - * @return false + * @return {@code false} */ public boolean contains(Object o) { return false; } /** - * Always returns false. - * A SynchronousQueue has no internal capacity. + * Always returns {@code false}. + * A {@code SynchronousQueue} has no internal capacity. * * @param o the element to remove - * @return false + * @return {@code false} */ public boolean remove(Object o) { return false; } /** - * Returns false unless the given collection is empty. - * A SynchronousQueue has no internal capacity. + * Returns {@code false} unless the given collection is empty. + * A {@code SynchronousQueue} has no internal capacity. * * @param c the collection - * @return false unless given collection is empty + * @return {@code false} unless given collection is empty */ public boolean containsAll(Collection c) { return c.isEmpty(); } /** - * Always returns false. - * A SynchronousQueue has no internal capacity. + * Always returns {@code false}. + * A {@code SynchronousQueue} has no internal capacity. * * @param c the collection - * @return false + * @return {@code false} */ public boolean removeAll(Collection c) { return false; } /** - * Always returns false. - * A SynchronousQueue has no internal capacity. + * Always returns {@code false}. + * A {@code SynchronousQueue} has no internal capacity. * * @param c the collection - * @return false + * @return {@code false} */ public boolean retainAll(Collection c) { return false; } /** - * Always returns null. - * A SynchronousQueue does not return elements + * Always returns {@code null}. + * A {@code SynchronousQueue} does not return elements * unless actively waited on. * - * @return null + * @return {@code null} */ public E peek() { return null; } /** - * Returns an empty iterator in which hasNext always returns - * false. + * Returns an empty iterator in which {@code hasNext} always returns + * {@code false}. * * @return an empty iterator */ @@ -1077,6 +1077,10 @@ public class SynchronousQueue extends AbstractQueue public void remove() { throw new IllegalStateException(); } } + public Spliterator spliterator() { + return Spliterators.emptySpliterator(); + } + /** * Returns a zero-length array. * @return a zero-length array @@ -1086,7 +1090,7 @@ public class SynchronousQueue extends AbstractQueue } /** - * Sets the zeroeth element of the specified array to null + * Sets the zeroeth element of the specified array to {@code null} * (if the array has non-zero length) and returns it. * * @param a the array diff --git a/src/share/classes/java/util/concurrent/TimeUnit.java b/src/share/classes/java/util/concurrent/TimeUnit.java index ab3aa854f35da19bf35c66ba2905c7c1c1654a5f..bd56b4835a747ca114d6797bc575a67b2638a67b 100644 --- a/src/share/classes/java/util/concurrent/TimeUnit.java +++ b/src/share/classes/java/util/concurrent/TimeUnit.java @@ -36,10 +36,10 @@ package java.util.concurrent; /** - * A TimeUnit represents time durations at a given unit of + * A {@code TimeUnit} represents time durations at a given unit of * granularity and provides utility methods to convert across units, * and to perform timing and delay operations in these units. A - * TimeUnit does not maintain time information, but only + * {@code TimeUnit} does not maintain time information, but only * helps organize and use time representations that may be maintained * separately across various contexts. A nanosecond is defined as one * thousandth of a microsecond, a microsecond as one thousandth of a @@ -47,7 +47,7 @@ package java.util.concurrent; * as sixty seconds, an hour as sixty minutes, and a day as twenty four * hours. * - *

A TimeUnit is mainly used to inform time-based methods + *

A {@code TimeUnit} is mainly used to inform time-based methods * how a given timing parameter should be interpreted. For example, * the following code will timeout in 50 milliseconds if the {@link * java.util.concurrent.locks.Lock lock} is not available: @@ -63,7 +63,7 @@ package java.util.concurrent; * * Note however, that there is no guarantee that a particular timeout * implementation will be able to notice the passage of time at the - * same granularity as the given TimeUnit. + * same granularity as the given {@code TimeUnit}. * * @since 1.5 * @author Doug Lea @@ -174,83 +174,82 @@ public enum TimeUnit { // etc. are not declared abstract but otherwise act as abstract methods. /** - * Convert the given time duration in the given unit to this - * unit. Conversions from finer to coarser granularities - * truncate, so lose precision. For example converting - * 999 milliseconds to seconds results in - * 0. Conversions from coarser to finer granularities - * with arguments that would numerically overflow saturate to - * Long.MIN_VALUE if negative or Long.MAX_VALUE - * if positive. + * Converts the given time duration in the given unit to this unit. + * Conversions from finer to coarser granularities truncate, so + * lose precision. For example, converting {@code 999} milliseconds + * to seconds results in {@code 0}. Conversions from coarser to + * finer granularities with arguments that would numerically + * overflow saturate to {@code Long.MIN_VALUE} if negative or + * {@code Long.MAX_VALUE} if positive. * *

For example, to convert 10 minutes to milliseconds, use: - * TimeUnit.MILLISECONDS.convert(10L, TimeUnit.MINUTES) + * {@code TimeUnit.MILLISECONDS.convert(10L, TimeUnit.MINUTES)} * - * @param sourceDuration the time duration in the given sourceUnit - * @param sourceUnit the unit of the sourceDuration argument + * @param sourceDuration the time duration in the given {@code sourceUnit} + * @param sourceUnit the unit of the {@code sourceDuration} argument * @return the converted duration in this unit, - * or Long.MIN_VALUE if conversion would negatively - * overflow, or Long.MAX_VALUE if it would positively overflow. + * or {@code Long.MIN_VALUE} if conversion would negatively + * overflow, or {@code Long.MAX_VALUE} if it would positively overflow. */ public long convert(long sourceDuration, TimeUnit sourceUnit) { throw new AbstractMethodError(); } /** - * Equivalent to NANOSECONDS.convert(duration, this). + * Equivalent to + * {@link #convert(long, TimeUnit) NANOSECONDS.convert(duration, this)}. * @param duration the duration * @return the converted duration, - * or Long.MIN_VALUE if conversion would negatively - * overflow, or Long.MAX_VALUE if it would positively overflow. - * @see #convert + * or {@code Long.MIN_VALUE} if conversion would negatively + * overflow, or {@code Long.MAX_VALUE} if it would positively overflow. */ public long toNanos(long duration) { throw new AbstractMethodError(); } /** - * Equivalent to MICROSECONDS.convert(duration, this). + * Equivalent to + * {@link #convert(long, TimeUnit) MICROSECONDS.convert(duration, this)}. * @param duration the duration * @return the converted duration, - * or Long.MIN_VALUE if conversion would negatively - * overflow, or Long.MAX_VALUE if it would positively overflow. - * @see #convert + * or {@code Long.MIN_VALUE} if conversion would negatively + * overflow, or {@code Long.MAX_VALUE} if it would positively overflow. */ public long toMicros(long duration) { throw new AbstractMethodError(); } /** - * Equivalent to MILLISECONDS.convert(duration, this). + * Equivalent to + * {@link #convert(long, TimeUnit) MILLISECONDS.convert(duration, this)}. * @param duration the duration * @return the converted duration, - * or Long.MIN_VALUE if conversion would negatively - * overflow, or Long.MAX_VALUE if it would positively overflow. - * @see #convert + * or {@code Long.MIN_VALUE} if conversion would negatively + * overflow, or {@code Long.MAX_VALUE} if it would positively overflow. */ public long toMillis(long duration) { throw new AbstractMethodError(); } /** - * Equivalent to SECONDS.convert(duration, this). + * Equivalent to + * {@link #convert(long, TimeUnit) SECONDS.convert(duration, this)}. * @param duration the duration * @return the converted duration, - * or Long.MIN_VALUE if conversion would negatively - * overflow, or Long.MAX_VALUE if it would positively overflow. - * @see #convert + * or {@code Long.MIN_VALUE} if conversion would negatively + * overflow, or {@code Long.MAX_VALUE} if it would positively overflow. */ public long toSeconds(long duration) { throw new AbstractMethodError(); } /** - * Equivalent to MINUTES.convert(duration, this). + * Equivalent to + * {@link #convert(long, TimeUnit) MINUTES.convert(duration, this)}. * @param duration the duration * @return the converted duration, - * or Long.MIN_VALUE if conversion would negatively - * overflow, or Long.MAX_VALUE if it would positively overflow. - * @see #convert + * or {@code Long.MIN_VALUE} if conversion would negatively + * overflow, or {@code Long.MAX_VALUE} if it would positively overflow. * @since 1.6 */ public long toMinutes(long duration) { @@ -258,12 +257,12 @@ public enum TimeUnit { } /** - * Equivalent to HOURS.convert(duration, this). + * Equivalent to + * {@link #convert(long, TimeUnit) HOURS.convert(duration, this)}. * @param duration the duration * @return the converted duration, - * or Long.MIN_VALUE if conversion would negatively - * overflow, or Long.MAX_VALUE if it would positively overflow. - * @see #convert + * or {@code Long.MIN_VALUE} if conversion would negatively + * overflow, or {@code Long.MAX_VALUE} if it would positively overflow. * @since 1.6 */ public long toHours(long duration) { @@ -271,10 +270,10 @@ public enum TimeUnit { } /** - * Equivalent to DAYS.convert(duration, this). + * Equivalent to + * {@link #convert(long, TimeUnit) DAYS.convert(duration, this)}. * @param duration the duration * @return the converted duration - * @see #convert * @since 1.6 */ public long toDays(long duration) { @@ -294,9 +293,9 @@ public enum TimeUnit { * Performs a timed {@link Object#wait(long, int) Object.wait} * using this time unit. * This is a convenience method that converts timeout arguments - * into the form required by the Object.wait method. + * into the form required by the {@code Object.wait} method. * - *

For example, you could implement a blocking poll + *

For example, you could implement a blocking {@code poll} * method (see {@link BlockingQueue#poll BlockingQueue.poll}) * using: * @@ -327,7 +326,7 @@ public enum TimeUnit { * Performs a timed {@link Thread#join(long, int) Thread.join} * using this time unit. * This is a convenience method that converts time arguments into the - * form required by the Thread.join method. + * form required by the {@code Thread.join} method. * * @param thread the thread to wait for * @param timeout the maximum time to wait. If less than @@ -347,7 +346,7 @@ public enum TimeUnit { * Performs a {@link Thread#sleep(long, int) Thread.sleep} using * this time unit. * This is a convenience method that converts time arguments into the - * form required by the Thread.sleep method. + * form required by the {@code Thread.sleep} method. * * @param timeout the minimum time to sleep. If less than * or equal to zero, do not sleep at all. diff --git a/src/share/classes/java/util/concurrent/TimeoutException.java b/src/share/classes/java/util/concurrent/TimeoutException.java index ed08990c7d6316d621db4ef36c1cea2684ba086c..b54c52b21f469e018a842e8dcf8a71b14fba6547 100644 --- a/src/share/classes/java/util/concurrent/TimeoutException.java +++ b/src/share/classes/java/util/concurrent/TimeoutException.java @@ -40,7 +40,7 @@ package java.util.concurrent; * operations for which a timeout is specified need a means to * indicate that the timeout has occurred. For many such operations it * is possible to return a value that indicates timeout; when that is - * not possible or desirable then TimeoutException should be + * not possible or desirable then {@code TimeoutException} should be * declared and thrown. * * @since 1.5 @@ -50,13 +50,13 @@ public class TimeoutException extends Exception { private static final long serialVersionUID = 1900926677490660714L; /** - * Constructs a TimeoutException with no specified detail + * Constructs a {@code TimeoutException} with no specified detail * message. */ public TimeoutException() {} /** - * Constructs a TimeoutException with the specified detail + * Constructs a {@code TimeoutException} with the specified detail * message. * * @param message the detail message diff --git a/src/share/classes/java/util/concurrent/package-info.java b/src/share/classes/java/util/concurrent/package-info.java index 4c56e0390df430598e0bd0bf50eb825528af2e38..b236c290b103ba159e51b54da35ddea4abd288f0 100644 --- a/src/share/classes/java/util/concurrent/package-info.java +++ b/src/share/classes/java/util/concurrent/package-info.java @@ -48,7 +48,7 @@ * * {@link java.util.concurrent.Executor} is a simple standardized * interface for defining custom thread-like subsystems, including - * thread pools, asynchronous IO, and lightweight task frameworks. + * thread pools, asynchronous I/O, and lightweight task frameworks. * Depending on which concrete Executor class is being used, tasks may * execute in a newly created thread, an existing task-execution thread, * or the thread calling {@link java.util.concurrent.Executor#execute @@ -102,8 +102,10 @@ *

Queues

* * The {@link java.util.concurrent.ConcurrentLinkedQueue} class - * supplies an efficient scalable thread-safe non-blocking FIFO - * queue. + * supplies an efficient scalable thread-safe non-blocking FIFO queue. + * The {@link java.util.concurrent.ConcurrentLinkedDeque} class is + * similar, but additionally supports the {@link java.util.Deque} + * interface. * *

Five implementations in {@code java.util.concurrent} support * the extended {@link java.util.concurrent.BlockingQueue} @@ -117,7 +119,7 @@ * for producer-consumer, messaging, parallel tasking, and * related concurrent designs. * - *

Extended interface {@link java.util.concurrent.TransferQueue}, + *

Extended interface {@link java.util.concurrent.TransferQueue}, * and implementation {@link java.util.concurrent.LinkedTransferQueue} * introduce a synchronous {@code transfer} method (along with related * features) in which a producer may optionally block awaiting its @@ -216,9 +218,9 @@ * it may (or may not) reflect any updates since the iterator was * created. * - *

Memory Consistency Properties

+ *

Memory Consistency Properties

* - * + * * Chapter 17 of the Java Language Specification defines the * happens-before relation on memory operations such as reads and * writes of shared variables. The results of a write by one thread are diff --git a/src/share/classes/java/util/logging/LogManager.java b/src/share/classes/java/util/logging/LogManager.java index 6ecfe69a98101a054a281da75ca1e39b5fffe88b..0d63468b3cc43db8defbceae83a33da9b3d1a603 100644 --- a/src/share/classes/java/util/logging/LogManager.java +++ b/src/share/classes/java/util/logging/LogManager.java @@ -193,13 +193,27 @@ public class LogManager { // Create and retain Logger for the root of the namespace. manager.rootLogger = manager.new RootLogger(); + // since by design the global manager's userContext and + // systemContext don't have their requiresDefaultLoggers + // flag set - we make sure to add the root logger to + // the global manager's default contexts here. manager.addLogger(manager.rootLogger); - manager.systemContext.addLocalLogger(manager.rootLogger); + manager.systemContext.addLocalLogger(manager.rootLogger, false); + manager.userContext.addLocalLogger(manager.rootLogger, false); // Adding the global Logger. Doing so in the Logger. // would deadlock with the LogManager.. - Logger.global.setLogManager(manager); - manager.addLogger(Logger.global); + // Do not call Logger.getGlobal() here as this might trigger + // the deadlock too. + @SuppressWarnings("deprecation") + final Logger global = Logger.global; + global.setLogManager(manager); + + // Make sure the global logger will be registered in the + // global manager's default contexts. + manager.addLogger(global); + manager.systemContext.addLocalLogger(global, false); + manager.userContext.addLocalLogger(global, false); // We don't call readConfiguration() here, as we may be running // very early in the JVM startup sequence. Instead readConfiguration @@ -401,7 +415,11 @@ public class LogManager { if (javaAwtAccess.isMainAppContext()) { context = userContext; } else { - context = new LoggerContext(); + // Create a new LoggerContext for the applet. + // The new logger context has its requiresDefaultLoggers + // flag set to true - so that these loggers will be + // lazily added when the context is firt accessed. + context = new LoggerContext(true); } javaAwtAccess.put(ecx, LoggerContext.class, context); } @@ -508,9 +526,13 @@ public class LogManager { private final Hashtable namedLoggers = new Hashtable<>(); // Tree of named Loggers private final LogNode root; - + private final boolean requiresDefaultLoggers; private LoggerContext() { + this(false); + } + private LoggerContext(boolean requiresDefaultLoggers) { this.root = new LogNode(null, this); + this.requiresDefaultLoggers = requiresDefaultLoggers; } Logger demandLogger(String name, String resourceBundleName) { @@ -519,7 +541,27 @@ public class LogManager { return manager.demandLogger(name, resourceBundleName, null); } + + // Due to subtle deadlock issues getUserContext() no longer + // calls addLocalLogger(rootLogger); + // Therefore - we need to add the default loggers later on. + // Checks that the context is properly initialized + // This is necessary before calling e.g. find(name) + // or getLoggerNames() + // + private void ensureInitialized() { + if (requiresDefaultLoggers) { + // Ensure that the root and global loggers are set. + ensureDefaultLogger(manager.rootLogger); + ensureDefaultLogger(Logger.global); + } + } + + synchronized Logger findLogger(String name) { + // ensure that this context is properly initialized before + // looking for loggers. + ensureInitialized(); LoggerWeakRef ref = namedLoggers.get(name); if (ref == null) { return null; @@ -533,21 +575,76 @@ public class LogManager { return logger; } - synchronized void ensureRootLogger(Logger logger) { - if (logger.getName().isEmpty()) + // This method is called before adding a logger to the + // context. + // 'logger' is the context that will be added. + // This method will ensure that the defaults loggers are added + // before adding 'logger'. + // + private void ensureAllDefaultLoggers(Logger logger) { + if (requiresDefaultLoggers) { + final String name = logger.getName(); + if (!name.isEmpty()) { + ensureDefaultLogger(manager.rootLogger); + } + if (!Logger.GLOBAL_LOGGER_NAME.equals(name)) { + ensureDefaultLogger(Logger.global); + } + } + } + + private void ensureDefaultLogger(Logger logger) { + // Used for lazy addition of root logger and global logger + // to a LoggerContext. + + // This check is simple sanity: we do not want that this + // method be called for anything else than Logger.global + // or owner.rootLogger. + if (!requiresDefaultLoggers || logger == null + || logger != Logger.global && logger != manager.rootLogger) { + + // the case where we have a non null logger which is neither + // Logger.global nor manager.rootLogger indicates a serious + // issue - as ensureDefaultLogger should never be called + // with any other loggers than one of these two (or null - if + // e.g manager.rootLogger is not yet initialized)... + assert logger == null; + return; + } - // during initialization, rootLogger is null when - // instantiating itself RootLogger - if (findLogger("") == null && manager.rootLogger != null) { - addLocalLogger(manager.rootLogger); + // Adds the logger if it's not already there. + if (!namedLoggers.containsKey(logger.getName())) { + // It is important to prevent addLocalLogger to + // call ensureAllDefaultLoggers when we're in the process + // off adding one of those default loggers - as this would + // immediately cause a stack overflow. + // Therefore we must pass addDefaultLoggersIfNeeded=false, + // even if requiresDefaultLoggers is true. + addLocalLogger(logger, false); } } + boolean addLocalLogger(Logger logger) { + // no need to add default loggers if it's not required + return addLocalLogger(logger, requiresDefaultLoggers); + } + // Add a logger to this context. This method will only set its level // and process parent loggers. It doesn't set its handlers. - synchronized boolean addLocalLogger(Logger logger) { - ensureRootLogger(logger); + synchronized boolean addLocalLogger(Logger logger, boolean addDefaultLoggersIfNeeded) { + // addDefaultLoggersIfNeeded serves to break recursion when adding + // default loggers. If we're adding one of the default loggers + // (we're being called from ensureDefaultLogger()) then + // addDefaultLoggersIfNeeded will be false: we don't want to + // call ensureAllDefaultLoggers again. + // + // Note: addDefaultLoggersIfNeeded can also be false when + // requiresDefaultLoggers is false - since calling + // ensureAllDefaultLoggers would have no effect in this case. + if (addDefaultLoggersIfNeeded) { + ensureAllDefaultLoggers(logger); + } final String name = logger.getName(); if (name == null) { @@ -615,6 +712,9 @@ public class LogManager { } synchronized Enumeration getLoggerNames() { + // ensure that this context is properly initialized before + // returning logger names. + ensureInitialized(); return namedLoggers.keys(); } diff --git a/src/share/classes/java/util/logging/Logger.java b/src/share/classes/java/util/logging/Logger.java index 11b0c0ce672381f5d8b106759024f07f35242a6e..1c959ecd6a1c42208b0d7f214e97ec537f44c5b4 100644 --- a/src/share/classes/java/util/logging/Logger.java +++ b/src/share/classes/java/util/logging/Logger.java @@ -232,6 +232,27 @@ public class Logger { * @since 1.7 */ public static final Logger getGlobal() { + // In order to break a cyclic dependence between the LogManager + // and Logger static initializers causing deadlocks, the global + // logger is created with a special constructor that does not + // initialize its log manager. + // + // If an application calls Logger.getGlobal() before any logger + // has been initialized, it is therefore possible that the + // LogManager class has not been initialized yet, and therefore + // Logger.global.manager will be null. + // + // In order to finish the initialization of the global logger, we + // will therefore call LogManager.getLogManager() here. + // + // Care must be taken *not* to call Logger.getGlobal() in + // LogManager static initializers in order to avoid such + // deadlocks. + // + if (global != null && global.manager == null) { + // Complete initialization of the global Logger. + global.manager = LogManager.getLogManager(); + } return global; } diff --git a/src/share/classes/java/util/spi/LocaleServiceProvider.java b/src/share/classes/java/util/spi/LocaleServiceProvider.java index c490dbadf8581b0c9a3f95113a2a9378a0cd0f38..87af9788f502eefcde2cbc9580047d1ee357a55a 100644 --- a/src/share/classes/java/util/spi/LocaleServiceProvider.java +++ b/src/share/classes/java/util/spi/LocaleServiceProvider.java @@ -42,7 +42,7 @@ import java.util.Locale; * interfaces to offer support for locales beyond the set of locales * supported by the Java runtime environment itself. *

- *

Packaging of Locale Sensitive Service Provider Implementations

+ *

Packaging of Locale Sensitive Service Provider Implementations

* Implementations of these locale sensitive services are packaged using the * Java Extension Mechanism * as installed extensions. A provider identifies itself with a @@ -165,7 +165,7 @@ public abstract class LocaleServiceProvider { /** * Returns {@code true} if the given {@code locale} is supported by * this locale service provider. The given {@code locale} may contain - * extensions that should be + * extensions that should be * taken into account for the support determination. * *

The default implementation returns {@code true} if the given {@code locale} diff --git a/src/share/classes/java/util/stream/SliceOps.java b/src/share/classes/java/util/stream/SliceOps.java index 78fd3d7f0d5f4508aabfb7ce08a4f240d25bf5e0..ac538f2d86c3340cf5494a27b5a4caee7c356afe 100644 --- a/src/share/classes/java/util/stream/SliceOps.java +++ b/src/share/classes/java/util/stream/SliceOps.java @@ -598,9 +598,9 @@ final class SliceOps { final Node.Builder nb = op.makeNodeBuilder(sizeIfKnown, generator); Sink opSink = op.opWrapSink(helper.getStreamAndOpFlags(), nb); helper.copyIntoWithCancel(helper.wrapSink(opSink), spliterator); - // It is necessary to truncate here since the result at the root - // can only be set once - return doTruncate(nb.build()); + // There is no need to truncate since the op performs the + // skipping and limiting of elements + return nb.build(); } else { Node node = helper.wrapAndCopyInto(helper.makeNodeBuilder(-1, generator), diff --git a/src/share/classes/javax/crypto/Cipher.java b/src/share/classes/javax/crypto/Cipher.java index 70d8d3466015f4f6e01c3b0ad60aa08af7931e1b..d16c579469cedef8983cd6e04559067b43d1038d 100644 --- a/src/share/classes/javax/crypto/Cipher.java +++ b/src/share/classes/javax/crypto/Cipher.java @@ -1135,7 +1135,7 @@ public class Cipher { * *

If this cipher (including its underlying feedback or padding scheme) * requires any random bytes (e.g., for parameter generation), it will get - * them using the {@link SecureRandom SecureRandom} + * them using the {@link java.security.SecureRandom} * implementation of the highest-priority * installed provider as the source of randomness. * (If none of the installed providers supply an implementation of @@ -1263,7 +1263,7 @@ public class Cipher { * *

If this cipher (including its underlying feedback or padding scheme) * requires any random bytes (e.g., for parameter generation), it will get - * them using the {@link SecureRandom SecureRandom} + * them using the {@link java.security.SecureRandom} * implementation of the highest-priority * installed provider as the source of randomness. * (If none of the installed providers supply an implementation of @@ -1400,7 +1400,7 @@ public class Cipher { * *

If this cipher (including its underlying feedback or padding scheme) * requires any random bytes (e.g., for parameter generation), it will get - * them using the {@link SecureRandom SecureRandom} + * them using the {@link java.security.SecureRandom} * implementation of the highest-priority * installed provider as the source of randomness. * (If none of the installed providers supply an implementation of diff --git a/src/share/classes/javax/crypto/CipherInputStream.java b/src/share/classes/javax/crypto/CipherInputStream.java index f062a1bc28ead1132aa7a56c7a2cd179221d19a9..a8b4152a5ae3591250c76fa68d487dcdf1d3a39a 100644 --- a/src/share/classes/javax/crypto/CipherInputStream.java +++ b/src/share/classes/javax/crypto/CipherInputStream.java @@ -245,7 +245,7 @@ public class CipherInputStream extends FilterInputStream { *

Fewer bytes than requested might be skipped. * The actual number of bytes skipped is equal to n or * the result of a call to - * {@link #available() available}, + * {@link #available() available}, * whichever is smaller. * If n is less than zero, no bytes are skipped. * diff --git a/src/share/classes/javax/crypto/ExemptionMechanism.java b/src/share/classes/javax/crypto/ExemptionMechanism.java index 6a7cc90d38956e477be2e27d19caa9e88d46a79d..72b0e75cfb11511c080f4737b26ab1f1e3d504f5 100644 --- a/src/share/classes/javax/crypto/ExemptionMechanism.java +++ b/src/share/classes/javax/crypto/ExemptionMechanism.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 @@ -116,7 +116,7 @@ public class ExemptionMechanism { * mechanism. * See the ExemptionMechanism section in the * + * "{@docRoot}/../technotes/guides/security/StandardNames.html#Exemption"> * Java Cryptography Architecture Standard Algorithm Name Documentation * for information about standard exemption mechanism names. * @@ -155,7 +155,7 @@ public class ExemptionMechanism { * @param algorithm the standard name of the requested exemption mechanism. * See the ExemptionMechanism section in the * + * "{@docRoot}/../technotes/guides/security/StandardNames.html#Exemption"> * Java Cryptography Architecture Standard Algorithm Name Documentation * for information about standard exemption mechanism names. * @@ -199,7 +199,7 @@ public class ExemptionMechanism { * @param algorithm the standard name of the requested exemption mechanism. * See the ExemptionMechanism section in the * + * "{@docRoot}/../technotes/guides/security/StandardNames.html#Exemption"> * Java Cryptography Architecture Standard Algorithm Name Documentation * for information about standard exemption mechanism names. * diff --git a/src/share/classes/javax/crypto/KeyAgreement.java b/src/share/classes/javax/crypto/KeyAgreement.java index cb85cb57d3111e9c75a5b66f7d386b6debd6789e..70221aa0c6e99d9ddf631eddc5ed66c3fae47e1b 100644 --- a/src/share/classes/javax/crypto/KeyAgreement.java +++ b/src/share/classes/javax/crypto/KeyAgreement.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 @@ -149,7 +149,7 @@ public class KeyAgreement { * algorithm. * See the KeyAgreement section in the - * Java Cryptography Architecture Standard Algorithm Name Documentation + * Java Cryptography Architecture Standard Algorithm Name Documentation * for information about standard algorithm names. * * @return the new KeyAgreement object. @@ -196,7 +196,7 @@ public class KeyAgreement { * algorithm. * See the KeyAgreement section in the - * Java Cryptography Architecture Standard Algorithm Name Documentation + * Java Cryptography Architecture Standard Algorithm Name Documentation * for information about standard algorithm names. * * @param provider the name of the provider. @@ -240,7 +240,7 @@ public class KeyAgreement { * algorithm. * See the KeyAgreement section in the - * Java Cryptography Architecture Standard Algorithm Name Documentation + * Java Cryptography Architecture Standard Algorithm Name Documentation * for information about standard algorithm names. * * @param provider the provider. @@ -418,7 +418,7 @@ public class KeyAgreement { * *

If this key agreement requires any random bytes, it will get * them using the - * {@link SecureRandom SecureRandom} + * {@link java.security.SecureRandom} * implementation of the highest-priority * installed provider as the source of randomness. * (If none of the installed providers supply an implementation of @@ -476,7 +476,7 @@ public class KeyAgreement { * *

If this key agreement requires any random bytes, it will get * them using the - * {@link SecureRandom SecureRandom} + * {@link java.security.SecureRandom} * implementation of the highest-priority * installed provider as the source of randomness. * (If none of the installed providers supply an implementation of diff --git a/src/share/classes/javax/crypto/KeyGenerator.java b/src/share/classes/javax/crypto/KeyGenerator.java index fee08392998b06427b30049c620f14a387afba09..961f0ea9eb3c33517b1ac46765709cf5a523262a 100644 --- a/src/share/classes/javax/crypto/KeyGenerator.java +++ b/src/share/classes/javax/crypto/KeyGenerator.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 @@ -398,7 +398,7 @@ public class KeyGenerator { * *

If this key generator requires any random bytes, it will get them * using the - * {@link SecureRandom SecureRandom} + * {@link java.security.SecureRandom} * implementation of the highest-priority installed * provider as the source of randomness. * (If none of the installed providers supply an implementation of @@ -463,7 +463,7 @@ public class KeyGenerator { * *

If this key generator requires any random bytes, it will get them * using the - * {@link SecureRandom SecureRandom} + * {@link java.security.SecureRandom} * implementation of the highest-priority installed * provider as the source of randomness. * (If none of the installed providers supply an implementation of diff --git a/src/share/classes/javax/crypto/NullCipher.java b/src/share/classes/javax/crypto/NullCipher.java index 39afb2ac4a850221274949e35b8ed00ee73276ad..5c8d2f54735d0201cd3c6bdcf914bfd7cab3f216 100644 --- a/src/share/classes/javax/crypto/NullCipher.java +++ b/src/share/classes/javax/crypto/NullCipher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2007, 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 @@ -38,6 +38,9 @@ package javax.crypto; public class NullCipher extends Cipher { + /** + * Creates a NullCipher object. + */ public NullCipher() { super(new NullCipherSpi(), null); } diff --git a/src/share/classes/javax/security/auth/Subject.java b/src/share/classes/javax/security/auth/Subject.java index 1caaad400e2b27b62a0e6303f2b1636a41173511..c72734edab32fed860a74f78b4c6fbcd79d3d668 100644 --- a/src/share/classes/javax/security/auth/Subject.java +++ b/src/share/classes/javax/security/auth/Subject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -325,6 +325,9 @@ public final class Subject implements java.io.Serializable { * action will run as. This parameter * may be null.

* + * @param the type of the value returned by the PrivilegedAction's + * {@code run} method. + * * @param action the code to be run as the specified * Subject.

* @@ -378,6 +381,9 @@ public final class Subject implements java.io.Serializable { * action will run as. This parameter * may be null.

* + * @param the type of the value returned by the + * PrivilegedExceptionAction's {@code run} method. + * * @param action the code to be run as the specified * Subject.

* @@ -434,6 +440,9 @@ public final class Subject implements java.io.Serializable { * action will run as. This parameter * may be null.

* + * @param the type of the value returned by the PrivilegedAction's + * {@code run} method. + * * @param action the code to be run as the specified * Subject.

* @@ -492,6 +501,9 @@ public final class Subject implements java.io.Serializable { * action will run as. This parameter * may be null.

* + * @param the type of the value returned by the + * PrivilegedExceptionAction's {@code run} method. + * * @param action the code to be run as the specified * Subject.

* @@ -590,6 +602,8 @@ public final class Subject implements java.io.Serializable { * *

* + * @param the type of the class modeled by {@code c} + * * @param c the returned Set of Principals will all be * instances of this class. * @@ -684,6 +698,8 @@ public final class Subject implements java.io.Serializable { * *

* + * @param the type of the class modeled by {@code c} + * * @param c the returned Set of public credentials will all be * instances of this class. * @@ -721,6 +737,8 @@ public final class Subject implements java.io.Serializable { * *

* + * @param the type of the class modeled by {@code c} + * * @param c the returned Set of private credentials will all be * instances of this class. * diff --git a/src/share/classes/javax/security/cert/X509Certificate.java b/src/share/classes/javax/security/cert/X509Certificate.java index 1f5af7e2d23f4218a8a3bc39d99be02daff36384..16fb0149801cba05bbd9b87d8d00bd84e8aefe2c 100644 --- a/src/share/classes/javax/security/cert/X509Certificate.java +++ b/src/share/classes/javax/security/cert/X509Certificate.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 @@ -159,9 +159,9 @@ public abstract class X509Certificate extends Certificate { * certificate is expected to be in the input stream. * Also, all X509Certificate * subclasses must provide a constructor of the form: - *

-     * public <subClass>(InputStream inStream) ...
-     * 
+ *
{@code
+     * public (InputStream inStream) ...
+     * }
* * @param inStream an input stream with the data to be read to * initialize the certificate. @@ -184,9 +184,9 @@ public abstract class X509Certificate extends Certificate { * *

Note: All X509Certificate * subclasses must provide a constructor of the form: - *

-     * public <subClass>(InputStream inStream) ...
-     * 
+ *
{@code
+     * public (InputStream inStream) ...
+     * }
* * @param certData a byte array containing the DER-encoded * certificate. @@ -255,10 +255,12 @@ public abstract class X509Certificate extends Certificate { * is valid. It is defined in * ASN.1 as: *
-     * validity             Validity

+ * validity Validity + * * Validity ::= SEQUENCE { * notBefore CertificateValidityDate, - * notAfter CertificateValidityDate }

+ * notAfter CertificateValidityDate } + * * CertificateValidityDate ::= CHOICE { * utcTime UTCTime, * generalTime GeneralizedTime } @@ -291,7 +293,8 @@ public abstract class X509Certificate extends Certificate { * Gets the version (version number) value from the * certificate. The ASN.1 definition for this is: *

-     * version         [0]  EXPLICIT Version DEFAULT v1

+ * version [0] EXPLICIT Version DEFAULT v1 + * * Version ::= INTEGER { v1(0), v2(1), v3(2) } *

* @@ -307,7 +310,7 @@ public abstract class X509Certificate extends Certificate { * serial number identify a unique certificate). * The ASN.1 definition for this is: *
-     * serialNumber     CertificateSerialNumber

+ * serialNumber CertificateSerialNumber * * CertificateSerialNumber ::= INTEGER *

@@ -325,7 +328,7 @@ public abstract class X509Certificate extends Certificate { * X.500 distinguished name (DN). * The ASN.1 definition for this is: *
-     * issuer    Name

+ * issuer Name * * Name ::= CHOICE { RDNSequence } * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName @@ -371,11 +374,12 @@ public abstract class X509Certificate extends Certificate { * the certificate. * The relevant ASN.1 definitions are: *

-     * validity             Validity

+ * validity Validity * * Validity ::= SEQUENCE { * notBefore CertificateValidityDate, - * notAfter CertificateValidityDate }

+ * notAfter CertificateValidityDate } + * * CertificateValidityDate ::= CHOICE { * utcTime UTCTime, * generalTime GeneralizedTime } @@ -401,7 +405,8 @@ public abstract class X509Certificate extends Certificate { * signature algorithm. An example is the string "SHA-1/DSA". * The ASN.1 definition for this is: *

-     * signatureAlgorithm   AlgorithmIdentifier

+ * signatureAlgorithm AlgorithmIdentifier + * * AlgorithmIdentifier ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, * parameters ANY DEFINED BY algorithm OPTIONAL } diff --git a/src/share/classes/sun/security/provider/certpath/RevocationChecker.java b/src/share/classes/sun/security/provider/certpath/RevocationChecker.java index 98d8a9d227237650e8fdcdeb7e454d502373c76d..05b517892feb9f6128b6e979333656366d3fd780 100644 --- a/src/share/classes/sun/security/provider/certpath/RevocationChecker.java +++ b/src/share/classes/sun/security/provider/certpath/RevocationChecker.java @@ -675,8 +675,12 @@ class RevocationChecker extends PKIXRevocationChecker { responderURI, respCert, params.date(), ocspExtensions); } - } catch (IOException e) { - throw new CertPathValidatorException(e); + } catch (Exception e) { + if (e instanceof CertPathValidatorException) { + throw (CertPathValidatorException) e; + } else { + throw new CertPathValidatorException(e); + } } RevocationStatus rs = diff --git a/test/java/lang/invoke/InvokeDynamicPrintArgs.java b/test/java/lang/invoke/InvokeDynamicPrintArgs.java index a318676ac299a6fc3bfafb9f0cbc3e02019ff063..a3f3cd2b69adcd761e468812c1ba2bad95dc4413 100644 --- a/test/java/lang/invoke/InvokeDynamicPrintArgs.java +++ b/test/java/lang/invoke/InvokeDynamicPrintArgs.java @@ -22,6 +22,7 @@ */ /* @test + * @bug 7050328 8007035 * @summary smoke test for invokedynamic instructions * @build indify.Indify * @compile InvokeDynamicPrintArgs.java @@ -42,6 +43,7 @@ import java.util.*; import java.io.*; import java.lang.invoke.*; +import java.security.*; import static java.lang.invoke.MethodHandles.*; import static java.lang.invoke.MethodType.*; @@ -62,17 +64,10 @@ public class InvokeDynamicPrintArgs { } private static void checkConstantRefs() throws Throwable { - // check some constant references: + // check some constant references to its self class assertEquals(MT_bsm(), MH_bsm().type()); assertEquals(MT_bsm2(), MH_bsm2().type()); - try { - assertEquals(MT_bsm(), non_MH_bsm().type()); - // if SM is installed, must throw before this point - assertEquals(false, System.getSecurityManager() != null); - } catch (SecurityException ex) { - // if SM is installed, must throw to this point - assertEquals(true, System.getSecurityManager() != null); - } + assertEquals(MT_bsm(), non_MH_bsm().type()); } private static void assertEquals(Object exp, Object act) { if (exp == act || (exp != null && exp.equals(act))) return; @@ -80,21 +75,8 @@ public class InvokeDynamicPrintArgs { } private static void setSM() { - // Test for severe security manager interactions (7050328). - class SM extends SecurityManager { - public void checkPackageAccess(String pkg) { - if (pkg.startsWith("test.")) - throw new SecurityException("checkPackageAccess "+pkg); - } - public void checkMemberAccess(Class clazz, int which) { - if (clazz == InvokeDynamicPrintArgs.class) - throw new SecurityException("checkMemberAccess "+clazz.getName()+" #"+which); - } - // allow these others: - public void checkPermission(java.security.Permission perm) { - } - } - System.setSecurityManager(new SM()); + Policy.setPolicy(new TestPolicy()); + System.setSecurityManager(new SecurityManager()); } private static PrintStream oldOut; @@ -250,4 +232,22 @@ public class InvokeDynamicPrintArgs { if (System.getProperty("InvokeDynamicPrintArgs.allow-untransformed") != null) return; throw new AssertionError("this code should be statically transformed away by Indify"); } + + static class TestPolicy extends Policy { + final PermissionCollection permissions = new Permissions(); + TestPolicy() { + permissions.add(new java.io.FilePermission("<>", "read")); + } + public PermissionCollection getPermissions(ProtectionDomain domain) { + return permissions; + } + + public PermissionCollection getPermissions(CodeSource codesource) { + return permissions; + } + + public boolean implies(ProtectionDomain domain, Permission perm) { + return permissions.implies(perm); + } + } } diff --git a/test/java/lang/invoke/TestCatchExceptionWithVarargs.java b/test/java/lang/invoke/TestCatchExceptionWithVarargs.java new file mode 100644 index 0000000000000000000000000000000000000000..a3df17c0f70bd9ba5ac62215c88841567be766fd --- /dev/null +++ b/test/java/lang/invoke/TestCatchExceptionWithVarargs.java @@ -0,0 +1,97 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8019184 + * @summary MethodHandles.catchException() fails when methods have 8 args + varargs + */ + +import java.util.*; +import java.lang.invoke.*; + +public class TestCatchExceptionWithVarargs { + + private static final Class CLASS = TestCatchExceptionWithVarargs.class; + private static final int MAX_MH_ARITY = 254; + + public static MethodHandle target; + public static MethodHandle handler; + + private static Object firstArg; + + static class MyException extends Exception { + } + + public static Object target(Object... a) throws Exception { + if (a[0] != firstArg) { + throw new AssertionError("first argument different than expected: " + a[0] + " != " + firstArg); + } + throw new MyException(); + } + + public static Object handler(Object... a) { + if (a[0] != firstArg) { + throw new AssertionError("first argument different than expected: " + a[0] + " != " + firstArg); + } + return a[0]; + } + + static { + try { + MethodType mtype = MethodType.methodType(Object.class, Object[].class); + target = MethodHandles.lookup().findStatic(CLASS, "target", mtype); + handler = MethodHandles.lookup().findStatic(CLASS, "handler", mtype); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public static void main(String[] args) throws Throwable { + List> ptypes = new LinkedList<>(); + ptypes.add(Object[].class); + + // We use MAX_MH_ARITY - 1 here to account for the Object[] argument. + for (int i = 1; i < MAX_MH_ARITY - 1; i++) { + ptypes.add(0, Object.class); + + MethodHandle targetWithArgs = target.asType(MethodType.methodType(Object.class, ptypes)); + MethodHandle handlerWithArgs = handler.asType(MethodType.methodType(Object.class, ptypes)); + handlerWithArgs = MethodHandles.dropArguments(handlerWithArgs, 0, MyException.class); + + MethodHandle gwc1 = MethodHandles.catchException(targetWithArgs, MyException.class, handlerWithArgs); + + // The next line throws an IllegalArgumentException if there is a bug. + MethodHandle gwc2 = MethodHandles.catchException(gwc1, MyException.class, handlerWithArgs); + + // This is only to verify that the method handles can actually be invoked and do the right thing. + firstArg = new Object(); + Object o = gwc2.asSpreader(Object[].class, ptypes.size() - 1).invoke(firstArg, new Object[i]); + if (o != firstArg) { + throw new AssertionError("return value different than expected: " + o + " != " + firstArg); + } + } + } +} diff --git a/test/java/lang/invoke/TestPrivateMember.java b/test/java/lang/invoke/TestPrivateMember.java new file mode 100644 index 0000000000000000000000000000000000000000..f2c0bc160d7f0bc53f2b9f9adb2383541cbb0bab --- /dev/null +++ b/test/java/lang/invoke/TestPrivateMember.java @@ -0,0 +1,57 @@ +/* + * 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. + * + * 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. + */ + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +/** + * @test + * @bug 8007035 + * @summary Test MethodHandle of a private member + * + * @run main TestPrivateMember + */ + +public class TestPrivateMember { + public static void main(String... args) throws Throwable { + System.setSecurityManager(new SecurityManager()); + TestPrivateMember t = new TestPrivateMember(); + t.test(); + } + + public TestPrivateMember() { + } + + public void test() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType mt = MethodType.methodType(void.class); + try { + MethodHandle mh = lookup.findStatic(Class.class, "checkInitted", mt); + throw new RuntimeException("IllegalAccessException not thrown"); + } catch (IllegalAccessException e) { + // okay + System.out.println("Expected exception: " + e.getMessage()); + } + } +} diff --git a/test/java/security/cert/CertPathValidator/OCSP/FailoverToCRL.java b/test/java/security/cert/CertPathValidator/OCSP/FailoverToCRL.java index df69edc7f2254199319f7c87b15f2c97accea723..25eaab56eea0737a5db6ba832f66257a253a2524 100644 --- a/test/java/security/cert/CertPathValidator/OCSP/FailoverToCRL.java +++ b/test/java/security/cert/CertPathValidator/OCSP/FailoverToCRL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 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 @@ -23,7 +23,7 @@ /** * @test - * @bug 6383095 + * @bug 6383095 8019259 * @summary CRL revoked certificate failures masked by OCSP failures * * Note that the certificate validity is from Mar 16 14:55:35 2009 GMT to @@ -254,12 +254,32 @@ public class FailoverToCRL { CertPathValidator validator = CertPathValidator.getInstance("PKIX"); try { + System.out.println("Validating cert via OCSP: no responder URL"); validator.validate(path, params); } catch (CertPathValidatorException cpve) { if (cpve.getReason() != BasicReason.REVOKED) { throw new Exception( - "unexpect exception, should be a REVOKED CPVE", cpve); + "unexpected exception, should be a REVOKED CPVE", cpve); } + System.out.println(" successful failover to using CRLs"); + } + + java.security.cert.PKIXRevocationChecker revocationChecker = + (java.security.cert.PKIXRevocationChecker) + validator.getRevocationChecker(); + revocationChecker.setOCSPResponder( + new java.net.URI("bad_ocsp_responder_url")); + params.addCertPathChecker(revocationChecker); + + try { + System.out.println("Validating cert via OCSP: bad responder URL"); + validator.validate(path, params); + } catch (CertPathValidatorException cpve) { + if (cpve.getReason() != BasicReason.REVOKED) { + throw new Exception( + "unexpected exception, should be a REVOKED CPVE", cpve); + } + System.out.println(" successful failover to using CRLs"); } } } diff --git a/test/java/util/logging/LogManagerInstanceTest.java b/test/java/util/logging/LogManagerInstanceTest.java index 3d3714d56566f7849a925d608ff00c96a8d0d30f..c6fbe1e56d7f46496a2c3b7fcaf379898ced4788 100644 --- a/test/java/util/logging/LogManagerInstanceTest.java +++ b/test/java/util/logging/LogManagerInstanceTest.java @@ -63,7 +63,7 @@ public class LogManagerInstanceTest { if (!super.addLogger(root)) throw new RuntimeException("Fail to addLogger " + root); } else { - System.out.println("Root logger already exists"); + throw new RuntimeException("Root logger already exists"); } this.base = root; } diff --git a/test/java/util/logging/Logger/getGlobal/TestGetGlobal.java b/test/java/util/logging/Logger/getGlobal/TestGetGlobal.java new file mode 100644 index 0000000000000000000000000000000000000000..dd901ed5f92ac9a7e0d9086db460aae3b304e6c4 --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/TestGetGlobal.java @@ -0,0 +1,73 @@ +/* + * 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. + * + * 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. + */ +import java.util.Arrays; +import java.util.List; +import java.util.logging.Logger; + +/** + * @test + * @bug 7184195 + * @summary checks that java.util.logging.Logger.getGlobal().info() logs without configuration + * @build TestGetGlobal testgetglobal.HandlerImpl testgetglobal.LogManagerImpl1 testgetglobal.LogManagerImpl2 testgetglobal.LogManagerImpl3 testgetglobal.BadLogManagerImpl testgetglobal.DummyLogManagerImpl + * @run main/othervm/timeout=10 TestGetGlobal + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager TestGetGlobal + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl1 TestGetGlobal + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl1 TestGetGlobal + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl2 TestGetGlobal + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl2 TestGetGlobal + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl3 TestGetGlobal + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl3 TestGetGlobal + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.BadLogManagerImpl TestGetGlobal + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.BadLogManagerImpl TestGetGlobal + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.DummyLogManagerImpl TestGetGlobal + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.DummyLogManagerImpl TestGetGlobal + * @author danielfuchs + */ +public class TestGetGlobal { + + final static String[] messages = { + "1. This message should not appear on the console.", + "2. This message should appear on the console.", + "3. This message should now appear on the console too." + }; + + static { + System.setProperty("java.util.logging.config.file", + System.getProperty("test.src", ".") + java.io.File.separator + "logging.properties"); + } + + public static void main(String... args) { + + Logger.global.info(messages[0]); // at this point LogManager is not + // initialized yet, so this message should not appear. + Logger.getGlobal().info(messages[1]); // calling getGlobal() will + // initialize the LogManager - and thus this message should appear. + Logger.global.info(messages[2]); // Now that the LogManager is + // initialized, this message should appear too. + + final List expected = Arrays.asList(Arrays.copyOfRange(messages, 1, messages.length)); + if (!testgetglobal.HandlerImpl.received.equals(expected)) { + throw new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected); + } + } +} diff --git a/test/java/util/logging/Logger/getGlobal/TestGetGlobalByName.java b/test/java/util/logging/Logger/getGlobal/TestGetGlobalByName.java new file mode 100644 index 0000000000000000000000000000000000000000..62580a8bc3bf2f77ea6f7fc8b38f353b23826489 --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/TestGetGlobalByName.java @@ -0,0 +1,73 @@ +/* + * 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. + * + * 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. + */ +import java.util.Arrays; +import java.util.List; +import java.util.logging.Logger; + +/** + * @test + * @bug 7184195 + * @summary checks that java.util.logging.Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info() logs without configuration + * @build TestGetGlobalByName testgetglobal.HandlerImpl testgetglobal.LogManagerImpl1 testgetglobal.LogManagerImpl2 testgetglobal.LogManagerImpl3 testgetglobal.BadLogManagerImpl testgetglobal.DummyLogManagerImpl + * @run main/othervm/timeout=10 TestGetGlobalByName + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager TestGetGlobalByName + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl1 TestGetGlobalByName + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl TestGetGlobalByName + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl2 TestGetGlobalByName + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl2 TestGetGlobalByName + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl3 TestGetGlobalByName + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl3 TestGetGlobalByName + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.BadLogManagerImpl TestGetGlobalByName + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.BadLogManagerImpl TestGetGlobalByName + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.DummyLogManagerImpl TestGetGlobalByName + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.DummyLogManagerImpl TestGetGlobalByName + * @author danielfuchs + */ +public class TestGetGlobalByName { + + final static String[] messages = { + "1. This message should not appear on the console.", + "2. This message should appear on the console.", + "3. This message should now appear on the console too." + }; + + static { + System.setProperty("java.util.logging.config.file", + System.getProperty("test.src", ".") + java.io.File.separator + "logging.properties"); + } + + public static void main(String... args) { + + Logger.global.info(messages[0]); // at this point LogManager is not + // initialized yet, so this message should not appear. + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info(messages[1]); // calling getLogger() will + // initialize the LogManager - and thus this message should appear. + Logger.global.info(messages[2]); // Now that the LogManager is + // initialized, this message should appear too. + + final List expected = Arrays.asList(Arrays.copyOfRange(messages, 1, messages.length)); + if (!testgetglobal.HandlerImpl.received.equals(expected)) { + throw new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected); + } + } +} diff --git a/test/java/util/logging/Logger/getGlobal/TestGetGlobalConcurrent.java b/test/java/util/logging/Logger/getGlobal/TestGetGlobalConcurrent.java new file mode 100644 index 0000000000000000000000000000000000000000..4ef38ce36116e66122ade5cb3a968bfd756e822c --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/TestGetGlobalConcurrent.java @@ -0,0 +1,184 @@ +/* + * 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. + * + * 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. + */ +import java.util.Arrays; +import java.util.List; +import java.util.logging.Logger; + +/** + * @test + * @bug 7184195 + * @summary checks that java.util.logging.Logger.getGlobal().info() logs without configuration + * @build TestGetGlobalConcurrent testgetglobal.HandlerImpl testgetglobal.LogManagerImpl1 testgetglobal.LogManagerImpl2 testgetglobal.LogManagerImpl3 testgetglobal.BadLogManagerImpl testgetglobal.DummyLogManagerImpl + * @run main/othervm/timeout=10 TestGetGlobalConcurrent + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager TestGetGlobalConcurrent + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl TestGetGlobalConcurrent + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl TestGetGlobalConcurrent + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl2 TestGetGlobalConcurrent + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl2 TestGetGlobalConcurrent + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.LogManagerImpl3 TestGetGlobalConcurrent + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.LogManagerImpl3 TestGetGlobalConcurrent + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.BadLogManagerImpl TestGetGlobalConcurrent + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.BadLogManagerImpl TestGetGlobalConcurrent + * @run main/othervm/timeout=10 -Djava.util.logging.manager=testgetglobal.DummyLogManagerImpl TestGetGlobalConcurrent + * @run main/othervm/timeout=10/policy=policy -Djava.security.manager -Djava.util.logging.manager=testgetglobal.DummyLogManagerImpl TestGetGlobalConcurrent + * @author danielfuchs + */ +public class TestGetGlobalConcurrent { + + final static String[] messages = { + "1. This message should not appear on the console.", + "2. This message should appear on the console.", + "3. This message should now appear on the console too.", + "4. This message should appear on the console.", + "5. This message should now appear on the console too.", + "6. This message should appear on the console.", + "7. This message should now appear on the console too.", + "8. This message should appear on the console.", + "9. This message should now appear on the console too." + }; + + static { + System.setProperty("java.util.logging.config.file", + System.getProperty("test.src", ".") + java.io.File.separator + "logging.properties"); + } + + public static void test1() { + final int nb = 1; + final int i = 2*nb + 1; + Logger.getGlobal().info(messages[i]); // calling getGlobal() will + // initialize the LogManager - and thus this message should appear. + Logger.global.info(messages[i+1]); // Now that the LogManager is + // initialized, this message should appear too. + + final List expected = Arrays.asList(Arrays.copyOfRange(messages, i, i+2)); + if (!testgetglobal.HandlerImpl.received.containsAll(expected)) { + fail(new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected)); + } + } + public static void test2() { + final int nb = 2; + final int i = 2*nb + 1; + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info(messages[i]); // calling getGlobal() will + // initialize the LogManager - and thus this message should appear. + Logger.global.info(messages[i+1]); // Now that the LogManager is + // initialized, this message should appear too. + + final List expected = Arrays.asList(Arrays.copyOfRange(messages, i, i+2)); + if (!testgetglobal.HandlerImpl.received.containsAll(expected)) { + fail(new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected)); + } + } + public static void test3() { + final int nb = 3; + final int i = 2*nb + 1; + java.util.logging.LogManager.getLogManager(); + Logger.getGlobal().info(messages[i]); // calling getGlobal() will + // initialize the LogManager - and thus this message should appear. + Logger.global.info(messages[i+1]); // Now that the LogManager is + // initialized, this message should appear too. + + final List expected = Arrays.asList(Arrays.copyOfRange(messages, i, i+2)); + if (!testgetglobal.HandlerImpl.received.containsAll(expected)) { + fail(new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected)); + } + } + public static void test4() { + log = new MyLogger("foo.bar"); + java.util.logging.LogManager.getLogManager().addLogger(log); + } + + + private static volatile Throwable failed = null; + private static volatile Logger log = null; + + public static class MyLogger extends Logger { + public MyLogger(String name) { + super(name, null); + } + } + + public static void fail(Throwable failure) { + failure.printStackTrace(); + if (failed == null) failed = failure; + } + + public static class WaitAndRun implements Runnable { + private final Runnable run; + public WaitAndRun(Runnable run) { + this.run = run; + } + public void run() { + try { + Thread.sleep(10); + run.run(); + } catch (Exception | Error x) { + fail(x); + } + } + } + + final static class Run1 implements Runnable { + public void run() { test1(); } + } + final static class Run2 implements Runnable { + public void run() { test2(); } + } + final static class Run3 implements Runnable { + public void run() { test3(); } + } + final static class Run4 implements Runnable { + public void run() { test4(); } + } + + public static void main(String... args) throws Exception { + + final Thread t1 = new Thread(new WaitAndRun(new Run1()), "test1"); + final Thread t2 = new Thread(new WaitAndRun(new Run2()), "test2"); + final Thread t3 = new Thread(new WaitAndRun(new Run3()), "test3"); + final Thread t4 = new Thread(new WaitAndRun(new Run4()), "test4"); + + t1.setDaemon(true); t2.setDaemon(true); t3.setDaemon(true); t4.setDaemon(true); + t1.start(); t2.start(); t3.start(); t4.start(); + + Thread.sleep(10); + + Logger.getGlobal().info(messages[1]); // calling getGlobal() will + // initialize the LogManager - and thus this message should appear. + Logger.global.info(messages[2]); // Now that the LogManager is + // initialized, this message should appear too. + + final List expected = Arrays.asList(Arrays.copyOfRange(messages, 1, 3)); + if (!testgetglobal.HandlerImpl.received.containsAll(expected)) { + throw new Error("Unexpected message list: "+testgetglobal.HandlerImpl.received+" vs "+ expected); + } + + + t1.join(); t2.join(); t3.join(); t4.join(); + + if (failed != null) { + throw new Error("Test failed.", failed); + } + + System.out.println("Test passed"); + } +} diff --git a/test/java/util/logging/Logger/getGlobal/logging.properties b/test/java/util/logging/Logger/getGlobal/logging.properties new file mode 100644 index 0000000000000000000000000000000000000000..0e201a9c60ce0e0716ce4ce179fb52d550ff720d --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/logging.properties @@ -0,0 +1,2 @@ +.level=INFO +handlers=testgetglobal.HandlerImpl diff --git a/test/java/util/logging/Logger/getGlobal/policy b/test/java/util/logging/Logger/getGlobal/policy new file mode 100644 index 0000000000000000000000000000000000000000..bcb7cde0da364e180b1cb3c64a36484feb7a02fe --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/policy @@ -0,0 +1,7 @@ +grant { + permission java.util.PropertyPermission "java.util.logging.config.file", "write"; + permission java.util.PropertyPermission "test.src", "read"; + permission java.lang.RuntimePermission "setContextClassLoader"; + permission java.lang.RuntimePermission "shutdownHooks"; + permission java.util.logging.LoggingPermission "control"; +}; diff --git a/test/java/util/logging/Logger/getGlobal/testgetglobal/BadLogManagerImpl.java b/test/java/util/logging/Logger/getGlobal/testgetglobal/BadLogManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..e3fd771ffd763b0e362066bd6605e8ccb514b938 --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/testgetglobal/BadLogManagerImpl.java @@ -0,0 +1,46 @@ +/* + * 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. + * + * 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 testgetglobal; + +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * This class is used to verify that calling Logger.getLogger(Logger.GLOBAL_LOGGER_NAME) + * in the constructor of a LogManager subclass installed as default + * LogManager does not cause issues beyond throwing the expected NPE. + * In that case the default LogManager class will simply be used. + * @author danielfuchs + */ +public class BadLogManagerImpl extends LogManager { + + final Logger globalLogger; + public BadLogManagerImpl() { + // The call below should generate an NPE, which will be + // catched in LogManager initializer. + globalLogger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + System.err.println("Global is: " + globalLogger); + throw new Error("Should not have reached here"); + } + +} diff --git a/test/java/util/logging/Logger/getGlobal/testgetglobal/DummyLogManagerImpl.java b/test/java/util/logging/Logger/getGlobal/testgetglobal/DummyLogManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..e3a48372fa729b665b9aa9d6b8210637f8ee146e --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/testgetglobal/DummyLogManagerImpl.java @@ -0,0 +1,35 @@ +/* + * 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. + * + * 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 testgetglobal; + +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * A dummy LogManager subclass that does nothing beyond extending LogManager. + * @author danielfuchs + */ +public class DummyLogManagerImpl extends LogManager { + + +} diff --git a/test/java/util/logging/Logger/getGlobal/testgetglobal/HandlerImpl.java b/test/java/util/logging/Logger/getGlobal/testgetglobal/HandlerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..f8737acb25f47a905019870b938b1c18cfbf6b58 --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/testgetglobal/HandlerImpl.java @@ -0,0 +1,46 @@ +/* + * 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. + * + * 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 testgetglobal; + +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.List; +import java.util.logging.ConsoleHandler; +import java.util.logging.LogRecord; + +/** + * + * @author danielfuchs + */ +public class HandlerImpl extends ConsoleHandler { + + public final static List received = new CopyOnWriteArrayList<>(); + + public HandlerImpl() { + } + + @Override + public void publish(LogRecord record) { + received.add(record.getMessage()); + super.publish(record); + } +} diff --git a/test/java/util/logging/Logger/getGlobal/testgetglobal/LogManagerImpl1.java b/test/java/util/logging/Logger/getGlobal/testgetglobal/LogManagerImpl1.java new file mode 100644 index 0000000000000000000000000000000000000000..9f3f6432a58cec4907028b2ad1ea40c8ca395715 --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/testgetglobal/LogManagerImpl1.java @@ -0,0 +1,42 @@ +/* + * 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. + * + * 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 testgetglobal; + +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * This class is used to verify that calling Logger.getGlobal() in the static + * initializer of a LogManager subclass installed as default LogManager + * does not cause issues. + * @author danielfuchs + */ +public class LogManagerImpl1 extends LogManager { + + static final Logger global; + static { + global = Logger.getGlobal(); + System.err.println("Global is: " + global); + } + +} diff --git a/test/java/util/logging/Logger/getGlobal/testgetglobal/LogManagerImpl2.java b/test/java/util/logging/Logger/getGlobal/testgetglobal/LogManagerImpl2.java new file mode 100644 index 0000000000000000000000000000000000000000..dd9d7aeca7ffc4b6a1d4876bce8af0df5dc3fb71 --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/testgetglobal/LogManagerImpl2.java @@ -0,0 +1,42 @@ +/* + * 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. + * + * 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 testgetglobal; + +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * This class is used to verify that calling Logger.getGlobal() in the constructor + * initializer of a LogManager subclass installed as default LogManager + * does not cause issues. + * @author danielfuchs + */ +public class LogManagerImpl2 extends LogManager { + + final Logger globalLogger; + public LogManagerImpl2() { + globalLogger = Logger.getGlobal(); + System.err.println("Global is: " + globalLogger); + } + +} diff --git a/test/java/util/logging/Logger/getGlobal/testgetglobal/LogManagerImpl3.java b/test/java/util/logging/Logger/getGlobal/testgetglobal/LogManagerImpl3.java new file mode 100644 index 0000000000000000000000000000000000000000..f5ce01cd90dcb01b18f4b9ec3b029f804260932a --- /dev/null +++ b/test/java/util/logging/Logger/getGlobal/testgetglobal/LogManagerImpl3.java @@ -0,0 +1,59 @@ +/* + * 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. + * + * 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 testgetglobal; + +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * This class is used to verify that calling Logger.getLogger(Logger.GLOBAL_LOGGER_NAME) + * in the static initializer of a LogManager subclass installed as default + * LogManager does not cause issues beyond throwing the expected NPE. + * @author danielfuchs + */ +public class LogManagerImpl3 extends LogManager { + + static final Logger global; + static { + Logger g = null; + try { + g = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + throw new Error("Should not have reached here"); + } catch (Exception x) { + // This is to be expected: Logger.getLogger(Logger.GLOBAL_LOGGER_NAME) + // will call LogManager.getLogManager() which will return null, since + // we haven't manage to do new LogManagerImpl3() yet. + // + System.err.println("Got expected exception - you cannot call" + + " Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)" + + " in LogManager subclass static initializer: " + x); + x.printStackTrace(); + } + if (g == null) { + g = Logger.getGlobal(); + } + global = g; + System.err.println("Global is: " + global); + } + +} diff --git a/test/java/util/logging/TestAppletLoggerContext.java b/test/java/util/logging/TestAppletLoggerContext.java new file mode 100644 index 0000000000000000000000000000000000000000..c7f3d4f49911c2d98400177610a4ca97e491e25a --- /dev/null +++ b/test/java/util/logging/TestAppletLoggerContext.java @@ -0,0 +1,610 @@ +/* + * 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. + * + * 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. + */ + +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.ProtectionDomain; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import java.util.logging.LoggingPermission; +import sun.misc.JavaAWTAccess; +import sun.misc.SharedSecrets; + +/* + * @test + * @bug 8017174 8010727 + * @summary NPE when using Logger.getAnonymousLogger or + * LogManager.getLogManager().getLogger + * + * @run main/othervm -Dtest.security=off TestAppletLoggerContext LoadingApplet + * @run main/othervm -Dtest.security=on TestAppletLoggerContext LoadingApplet + * @run main/othervm -Dtest.security=off TestAppletLoggerContext LoadingMain + * @run main/othervm -Dtest.security=on TestAppletLoggerContext LoadingMain + * @run main/othervm -Dtest.security=off TestAppletLoggerContext One + * @run main/othervm -Dtest.security=on TestAppletLoggerContext One + * @run main/othervm -Dtest.security=off TestAppletLoggerContext Two + * @run main/othervm -Dtest.security=on TestAppletLoggerContext Two + * @run main/othervm -Dtest.security=off TestAppletLoggerContext Three + * @run main/othervm -Dtest.security=on TestAppletLoggerContext Three + * @run main/othervm -Dtest.security=off TestAppletLoggerContext Four + * @run main/othervm -Dtest.security=on TestAppletLoggerContext Four + * @run main/othervm -Dtest.security=off TestAppletLoggerContext Five + * @run main/othervm -Dtest.security=on TestAppletLoggerContext Five + * @run main/othervm -Dtest.security=off TestAppletLoggerContext Six + * @run main/othervm -Dtest.security=on TestAppletLoggerContext Six + * @run main/othervm -Dtest.security=off TestAppletLoggerContext Seven + * @run main/othervm -Dtest.security=on TestAppletLoggerContext Seven + * @run main/othervm -Dtest.security=off TestAppletLoggerContext + * @run main/othervm -Dtest.security=on TestAppletLoggerContext + */ + +// NOTE: We run in other VM in order to 1. switch security manager and 2. cause +// LogManager class to be loaded anew. +public class TestAppletLoggerContext { + + // Avoids the hassle of dealing with files and system props... + static class SimplePolicy extends Policy { + private final Permissions perms; + public SimplePolicy(Permission... permissions) { + perms = new Permissions(); + for (Permission permission : permissions) { + perms.add(permission); + } + } + @Override + public PermissionCollection getPermissions(CodeSource cs) { + return perms; + } + @Override + public PermissionCollection getPermissions(ProtectionDomain pd) { + return perms; + } + @Override + public boolean implies(ProtectionDomain pd, Permission p) { + return perms.implies(p); + } + } + + // The bridge class initializes the logging system. + // It stubs the applet context in order to simulate context changes. + // + public static class Bridge { + + private static class JavaAWTAccessStub implements JavaAWTAccess { + boolean active = true; + + private static class TestExc { + private final Map map = new HashMap<>(); + void put(Object key, Object v) { map.put(key, v); } + Object get(Object key) { return map.get(key); } + void remove(Object o) { map.remove(o); } + public static TestExc exc(Object o) { + return TestExc.class.cast(o); + } + } + + TestExc exc; + TestExc global = new TestExc(); + + @Override + public Object getContext() { return active ? global : null; } + @Override + public Object getExecutionContext() { return active ? exc : null; } + @Override + public Object get(Object o, Object o1) { return TestExc.exc(o).get(o1); } + @Override + public void put(Object o, Object o1, Object o2) { TestExc.exc(o).put(o1, o2); } + @Override + public void remove(Object o, Object o1) { TestExc.exc(o).remove(o1); } + @Override + public Object get(Object o) { return global.get(o); } + @Override + public void put(Object o, Object o1) { global.put(o, o1); } + @Override + public void remove(Object o) { global.remove(o); } + @Override + public boolean isDisposed() { return false; } + @Override + public boolean isMainAppContext() { return exc == null; } + } + + final static JavaAWTAccessStub javaAwtAccess = new JavaAWTAccessStub(); + public static void init() { + SharedSecrets.setJavaAWTAccess(javaAwtAccess); + if (System.getProperty("test.security", "on").equals("on")) { + Policy p = new SimplePolicy(new LoggingPermission("control", null), + new RuntimePermission("setContextClassLoader"), + new RuntimePermission("shutdownHooks")); + Policy.setPolicy(p); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void changeContext() { + System.out.println("... Switching to a new applet context ..."); + javaAwtAccess.active = true; + javaAwtAccess.exc = new JavaAWTAccessStub.TestExc(); + } + + public static void desactivate() { + System.out.println("... Running with no applet context ..."); + javaAwtAccess.exc = null; + javaAwtAccess.active = false; + } + + public static class CustomAnonymousLogger extends Logger { + public CustomAnonymousLogger() { + this(""); + } + public CustomAnonymousLogger(String name) { + super(null, null); + System.out.println( " LogManager: " +LogManager.getLogManager()); + System.out.println( " getLogger: " +LogManager.getLogManager().getLogger(name)); + setParent(LogManager.getLogManager().getLogger(name)); + } + } + + public static class CustomLogger extends Logger { + CustomLogger(String name) { + super(name, null); + } + } + } + + public static enum TestCase { + LoadingApplet, LoadingMain, One, Two, Three, Four, Five, Six, Seven; + public void test() { + switch(this) { + // When run - each of these two tests must be + // run before any other tests and before each other. + case LoadingApplet: testLoadingApplet(); break; + case LoadingMain: testLoadingMain(); break; + case One: testOne(); break; + case Two: testTwo(); break; + case Three: testThree(); break; + case Four: testFour(); break; + case Five: testFive(); break; + case Six: testSix(); break; + case Seven: testSeven(); break; + } + } + public String describe() { + switch(this) { + case LoadingApplet: + return "Test that when the LogManager class is" + + " loaded in an applet thread first," + + "\n all LoggerContexts are correctly initialized"; + case LoadingMain: + return "Test that when the LogManager class is" + + " loaded in the main thread first," + + "\n all LoggerContexts are correctly initialized"; + case One: + return "Test that Logger.getAnonymousLogger()" + + " and new CustomAnonymousLogger() don't throw NPE"; + case Two: + return "Test that Logger.getLogger(\"\")" + + " does not return null nor throws NPE"; + case Three: + return "Test that LogManager.getLogManager().getLogger(\"\")" + + " does not return null nor throws NPE"; + case Four: + return "Test that Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)" + + " does not return null,\n and that" + + " new CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME)" + + " does not throw NPE"; + case Five: + return "Test that LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME)" + + "\n does not return null nor throws NPE"; + case Six: + return "Test that manager.getLogger(Logger.GLOBAL_LOGGER_NAME)" + + " returns null\n when manager is not the default" + + " LogManager instance.\n" + + "Test adding a new logger named \"global\" in that" + + " non default instance."; + case Seven: return "Test that manager.getLogger(\"\")" + + " returns null\n when manager is not the default" + + " LogManager instance.\n" + + "Test adding a new logger named \"\" in that" + + " non default instance."; + default: return "Undefined"; + } + } + }; + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + Bridge.init(); + EnumSet tests = EnumSet.noneOf(TestCase.class); + for (String arg : args) { + tests.add(TestCase.valueOf(arg)); + } + if (args.length == 0) { + tests = EnumSet.complementOf(EnumSet.of(TestCase.LoadingMain)); + } + final EnumSet loadingTests = + EnumSet.of(TestCase.LoadingApplet, TestCase.LoadingMain); + int testrun = 0; + for (TestCase test : tests) { + if (loadingTests.contains(test)) { + if (testrun > 0) { + throw new UnsupportedOperationException("Test case " + + test + " must be executed first!"); + } + } + System.out.println("Testing "+ test+": "); + System.out.println(test.describe()); + try { + test.test(); + } catch (Exception x) { + throw new Error(String.valueOf(test) + + (System.getSecurityManager() == null ? " without " : " with ") + + "security failed: "+x+"\n "+"FAILED: "+test.describe()+"\n", x); + } finally { + testrun++; + } + Bridge.changeContext(); + System.out.println("PASSED: "+ test); + } + } + + public static void testLoadingApplet() { + Bridge.changeContext(); + + Logger bar = new Bridge.CustomLogger("com.foo.Bar"); + LogManager.getLogManager().addLogger(bar); + assertNotNull(bar.getParent()); + testParent(bar); + testParent(LogManager.getLogManager().getLogger("global")); + testParent(LogManager.getLogManager().getLogger(bar.getName())); + + Bridge.desactivate(); + + Logger foo = new Bridge.CustomLogger("com.foo.Foo"); + boolean b = LogManager.getLogManager().addLogger(foo); + assertEquals(Boolean.TRUE, Boolean.valueOf(b)); + assertNotNull(foo.getParent()); + testParent(foo); + testParent(LogManager.getLogManager().getLogger("global")); + testParent(LogManager.getLogManager().getLogger(foo.getName())); + } + + public static void testLoadingMain() { + Bridge.desactivate(); + + Logger bar = new Bridge.CustomLogger("com.foo.Bar"); + LogManager.getLogManager().addLogger(bar); + assertNotNull(bar.getParent()); + testParent(bar); + testParent(LogManager.getLogManager().getLogger("global")); + testParent(LogManager.getLogManager().getLogger(bar.getName())); + + Bridge.changeContext(); + + Logger foo = new Bridge.CustomLogger("com.foo.Foo"); + boolean b = LogManager.getLogManager().addLogger(foo); + assertEquals(Boolean.TRUE, Boolean.valueOf(b)); + assertNotNull(foo.getParent()); + testParent(foo); + testParent(LogManager.getLogManager().getLogger("global")); + testParent(LogManager.getLogManager().getLogger(foo.getName())); + + } + + public static void testOne() { + for (int i=0; i<3 ; i++) { + Logger logger1 = Logger.getAnonymousLogger(); + Logger logger1b = Logger.getAnonymousLogger(); + Bridge.changeContext(); + Logger logger2 = Logger.getAnonymousLogger(); + Logger logger2b = Logger.getAnonymousLogger(); + Bridge.changeContext(); + Logger logger3 = new Bridge.CustomAnonymousLogger(); + Logger logger3b = new Bridge.CustomAnonymousLogger(); + Bridge.changeContext(); + Logger logger4 = new Bridge.CustomAnonymousLogger(); + Logger logger4b = new Bridge.CustomAnonymousLogger(); + } + } + + + public static void testTwo() { + for (int i=0; i<3 ; i++) { + Logger logger1 = Logger.getLogger(""); + Logger logger1b = Logger.getLogger(""); + assertNotNull(logger1); + assertNotNull(logger1b); + assertEquals(logger1, logger1b); + Bridge.changeContext(); + Logger logger2 = Logger.getLogger(""); + Logger logger2b = Logger.getLogger(""); + assertNotNull(logger2); + assertNotNull(logger2b); + assertEquals(logger2, logger2b); + assertEquals(logger1, logger2); + } + } + + public static void testThree() { + for (int i=0; i<3 ; i++) { + Logger logger1 = LogManager.getLogManager().getLogger(""); + Logger logger1b = LogManager.getLogManager().getLogger(""); + assertNotNull(logger1); + assertNotNull(logger1b); + assertEquals(logger1, logger1b); + Bridge.changeContext(); + Logger logger2 = LogManager.getLogManager().getLogger(""); + Logger logger2b = LogManager.getLogManager().getLogger(""); + assertNotNull(logger2); + assertNotNull(logger2b); + assertEquals(logger2, logger2b); + assertEquals(logger1, logger2); + } + } + + public static void testFour() { + for (int i=0; i<3 ; i++) { + Logger logger1 = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger1b = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + assertNotNull(logger1); + assertNotNull(logger1b); + assertEquals(logger1, logger1b); + Bridge.changeContext(); + + Logger logger2 = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger2b = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + assertNotNull(logger2); + assertNotNull(logger2b); + assertEquals(logger2, logger2b); + + assertEquals(logger1, logger2); + + Bridge.changeContext(); + Logger logger3 = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger3b = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); + Bridge.changeContext(); + Logger logger4 = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger4b = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); + } + } + + public static void testFive() { + for (int i=0; i<3 ; i++) { + Logger logger1 = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger1b = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); + assertNotNull(logger1); + assertNotNull(logger1b); + assertEquals(logger1, logger1b); + + Bridge.changeContext(); + + Logger logger2 = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger2b = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); + assertNotNull(logger2); + assertNotNull(logger2b); + assertEquals(logger2, logger2b); + + assertEquals(logger1, logger2); + } + } + + /** + * This test is designed to test the behavior of additional LogManager instances. + * It must be noted that if the security manager is off, then calling + * Bridge.changeContext() has actually no effect - which explains why we have + * some differences between the cases security manager on & security manager + * off. + **/ + public static void testSix() { + for (int i=0; i<3 ; i++) { + Bridge.desactivate(); + LogManager manager = new LogManager() {}; + Logger logger1 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger1b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + assertNull(logger1); + assertNull(logger1b); + Logger global = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); + manager.addLogger(global); + Logger logger2 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger2b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + assertNotNull(logger2); + assertNotNull(logger2b); + assertEquals(logger2, global); + assertEquals(logger2b, global); + assertNull(manager.getLogger("")); + assertNull(manager.getLogger("")); + + Bridge.changeContext(); + + // this is not a supported configuration: + // We are in an applet context with several log managers. + // We however need to check our assumptions... + + // Applet context => root logger and global logger are not null. + // root == LogManager.getLogManager().rootLogger + // global == Logger.global + + Logger logger3 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger3b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + assertNotNull(logger3); + assertNotNull(logger3b); + Logger expected = (System.getSecurityManager() != null + ? Logger.getGlobal() + : global); + assertEquals(logger3, expected); // in applet context, we will not see + // the LogManager's custom global logger added above... + assertEquals(logger3b, expected); // in applet context, we will not see + // the LogManager's custom global logger added above... + Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); + manager.addLogger(global2); // adding a global logger will not work in applet context + // we will always get back the global logger. + // this could be considered as a bug... + Logger logger4 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger4b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + assertNotNull(logger4); + assertNotNull(logger4b); + assertEquals(logger4, expected); // adding a global logger will not work in applet context + assertEquals(logger4b, expected); // adding a global logger will not work in applet context + + Logger logger5 = manager.getLogger(""); + Logger logger5b = manager.getLogger(""); + Logger expectedRoot = (System.getSecurityManager() != null + ? LogManager.getLogManager().getLogger("") + : null); + assertEquals(logger5, expectedRoot); + assertEquals(logger5b, expectedRoot); + + } + } + + /** + * This test is designed to test the behavior of additional LogManager instances. + * It must be noted that if the security manager is off, then calling + * Bridge.changeContext() has actually no effect - which explains why we have + * some differences between the cases security manager on & security manager + * off. + **/ + public static void testSeven() { + for (int i=0; i<3 ; i++) { + Bridge.desactivate(); + LogManager manager = new LogManager() {}; + Logger logger1 = manager.getLogger(""); + Logger logger1b = manager.getLogger(""); + assertNull(logger1); + assertNull(logger1b); + Logger global = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); + manager.addLogger(global); + Logger logger2 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger2b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + assertNotNull(logger2); + assertNotNull(logger2b); + assertEquals(logger2, global); + assertEquals(logger2b, global); + Logger logger3 = manager.getLogger(""); + Logger logger3b = manager.getLogger(""); + assertNull(logger3); + assertNull(logger3b); + Logger root = new Bridge.CustomLogger(""); + manager.addLogger(root); + Logger logger4 = manager.getLogger(""); + Logger logger4b = manager.getLogger(""); + assertNotNull(logger4); + assertNotNull(logger4b); + assertEquals(logger4, root); + assertEquals(logger4b, root); + + Bridge.changeContext(); + + // this is not a supported configuration: + // We are in an applet context with several log managers. + // We haowever need to check our assumptions... + + // Applet context => root logger and global logger are not null. + // root == LogManager.getLogManager().rootLogger + // global == Logger.global + + Logger logger5 = manager.getLogger(""); + Logger logger5b = manager.getLogger(""); + Logger expectedRoot = (System.getSecurityManager() != null + ? LogManager.getLogManager().getLogger("") + : root); + + assertNotNull(logger5); + assertNotNull(logger5b); + assertEquals(logger5, expectedRoot); + assertEquals(logger5b, expectedRoot); + if (System.getSecurityManager() != null) { + assertNotEquals(logger5, root); + assertNotEquals(logger5b, root); + } + + Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); + manager.addLogger(global2); // adding a global logger will not work in applet context + // we will always get back the global logger. + // this could be considered as a bug... + Logger logger6 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger logger6b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); + Logger expectedGlobal = (System.getSecurityManager() != null + ? Logger.getGlobal() + : global); + assertNotNull(logger6); + assertNotNull(logger6b); + assertEquals(logger6, expectedGlobal); // adding a global logger will not work in applet context + assertEquals(logger6b, expectedGlobal); // adding a global logger will not work in applet context + + Logger root2 = new Bridge.CustomLogger(""); + manager.addLogger(root2); // adding a root logger will not work in applet context + // we will always get back the default manager's root logger. + // this could be considered as a bug... + Logger logger7 = manager.getLogger(""); + Logger logger7b = manager.getLogger(""); + assertNotNull(logger7); + assertNotNull(logger7b); + assertEquals(logger7, expectedRoot); // adding a global logger will not work in applet context + assertEquals(logger7b, expectedRoot); // adding a global logger will not work in applet context + assertNotEquals(logger7, root2); + assertNotEquals(logger7b, root2); + } + } + + public static void testParent(Logger logger) { + Logger l = logger; + while (l.getParent() != null) { + l = l.getParent(); + } + assertEquals("", l.getName()); + } + + public static class TestError extends RuntimeException { + public TestError(String msg) { + super(msg); + } + } + + public static void assertNotNull(Object obj) { + if (obj == null) throw new NullPointerException(); + } + + public static void assertNull(Object obj) { + if (obj != null) throw new TestError("Null expected, got "+obj); + } + + public static void assertEquals(Object o1, Object o2) { + if (o1 != o2) { + throw new TestError(o1 + " != " + o2); + } + } + + public static void assertNotEquals(Object o1, Object o2) { + if (o1 == o2) { + throw new TestError(o1 + " == " + o2); + } + } +} diff --git a/test/java/util/stream/test/org/openjdk/tests/java/util/stream/SliceOpTest.java b/test/java/util/stream/test/org/openjdk/tests/java/util/stream/SliceOpTest.java index 29086fa351d947dce3198cf109e277a8afa14191..afa1b0126537cd3d50f84830b3658b306fdaf984 100644 --- a/test/java/util/stream/test/org/openjdk/tests/java/util/stream/SliceOpTest.java +++ b/test/java/util/stream/test/org/openjdk/tests/java/util/stream/SliceOpTest.java @@ -26,13 +26,16 @@ import org.testng.annotations.Test; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.DoubleStream; import java.util.stream.IntStream; import java.util.stream.LambdaTestHelpers; import java.util.stream.LongStream; import java.util.stream.OpTestCase; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import java.util.stream.StreamTestDataProvider; import java.util.stream.TestData; @@ -192,6 +195,53 @@ public class SliceOpTest extends OpTestCase { } } + public void testSkipLimitOpsWithNonSplittingSpliterator() { + class NonSplittingNotSubsizedOrderedSpliterator implements Spliterator { + Spliterator s; + + NonSplittingNotSubsizedOrderedSpliterator(Spliterator s) { + assert s.hasCharacteristics(Spliterator.ORDERED); + this.s = s; + } + + @Override + public boolean tryAdvance(Consumer action) { + return s.tryAdvance(action); + } + + @Override + public void forEachRemaining(Consumer action) { + s.forEachRemaining(action); + } + + @Override + public Spliterator trySplit() { + return null; + } + + @Override + public long estimateSize() { + return s.estimateSize(); + } + + @Override + public int characteristics() { + return s.characteristics() & ~(Spliterator.SUBSIZED); + } + + @Override + public Comparator getComparator() { + return s.getComparator(); + } + } + List list = IntStream.range(0, 100).boxed().collect(Collectors.toList()); + TestData.OfRef data = TestData.Factory.ofSupplier( + "Non splitting, not SUBSIZED, ORDERED, stream", + () -> StreamSupport.stream(new NonSplittingNotSubsizedOrderedSpliterator<>(list.spliterator()))); + + testSkipLimitOps("testSkipLimitOpsWithNonSplittingSpliterator", data); + } + @Test(dataProvider = "StreamTestData", dataProviderClass = StreamTestDataProvider.class) public void testLimitOps(String name, TestData.OfRef data) { List limits = sizes(data.size()); diff --git a/test/tools/pack200/AttributeTests.java b/test/tools/pack200/AttributeTests.java index bcf4c7c92825114a9f6943b00ef89b181cf08e2c..09b9986175cf090aaec9346c2e4d8d8975f60af0 100644 --- a/test/tools/pack200/AttributeTests.java +++ b/test/tools/pack200/AttributeTests.java @@ -37,6 +37,7 @@ public class AttributeTests { public static void main(String... args) throws Exception { test6746111(); testMethodParameters(); + Utils.cleanup(); } /* diff --git a/test/tools/pack200/BandIntegrity.java b/test/tools/pack200/BandIntegrity.java index 1145972267f11d1c4260d270b18301afe5cb611b..2a59f3b8c1397f119d7d918eda86f0d02adb59d4 100644 --- a/test/tools/pack200/BandIntegrity.java +++ b/test/tools/pack200/BandIntegrity.java @@ -40,7 +40,7 @@ import java.util.List; * the java packer and unpacker must be called in the same java instance. */ public class BandIntegrity { - public static void main(String... args) throws IOException { + public static void main(String... args) throws IOException { File testFile = new File("test.jar"); Utils.jar("cvf", testFile.getName(), "-C", Utils.TEST_CLS_DIR.getAbsolutePath(), @@ -56,6 +56,7 @@ public class BandIntegrity { Utils.createFile(configFile, scratch); File outFile = new File("out.jar"); Utils.repack(testFile, outFile, true, - "-v", "--config-file=" + configFile.getName()); + "-v", "--config-file=" + configFile.getName()); + Utils.cleanup(); } } diff --git a/test/tools/pack200/CommandLineTests.java b/test/tools/pack200/CommandLineTests.java index 4e0c8b1e0036ff23e755f74f042dd9ec3e235984..2a53b30904600f66237aaf26179b6c552ad50eee 100644 --- a/test/tools/pack200/CommandLineTests.java +++ b/test/tools/pack200/CommandLineTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 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 @@ -83,6 +83,11 @@ public class CommandLineTests { Utils.recursiveCopy(Utils.JavaSDK, EXP_SDK); creatConfigFile(); } + // cleanup the test area + static void cleanup() throws IOException { + Utils.recursiveDelete(EXP_SDK); + Utils.cleanup(); + } // Hopefully, this should be kept in sync with what the installer does. static void creatConfigFile() throws IOException { @@ -172,6 +177,7 @@ public class CommandLineTests { init(); testJRE(); testJDK(); + cleanup(); // cleanup only if we pass successfully } catch (IOException ioe) { throw new RuntimeException(ioe); } diff --git a/test/tools/pack200/InstructionTests.java b/test/tools/pack200/InstructionTests.java index 171fe4ee88a24e8377e9c84c4bcf80de42d7bb13..569b7a0acb09fbdb1c979f60ddd2d10b5e2d8ff8 100644 --- a/test/tools/pack200/InstructionTests.java +++ b/test/tools/pack200/InstructionTests.java @@ -35,6 +35,7 @@ import java.util.List; public class InstructionTests { public static void main(String... args) throws Exception { testInvokeOpCodes(); + Utils.cleanup(); } /* * the following should produce invokestatic and invokespecial diff --git a/test/tools/pack200/Pack200Props.java b/test/tools/pack200/Pack200Props.java index 61b718ea9d9d58959b916c6a1cda51342539e6df..50fb0b147430999c7962ef8a717a537448ec0d0b 100644 --- a/test/tools/pack200/Pack200Props.java +++ b/test/tools/pack200/Pack200Props.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -31,6 +31,7 @@ */ import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -45,11 +46,12 @@ import java.util.jar.Pack200.Packer; public class Pack200Props { - public static void main(String... args) { + public static void main(String... args) throws IOException { verifyDefaults(); File out = new File("test" + Utils.PACK_FILE_EXT); out.delete(); verifySegmentLimit(out); + Utils.cleanup(); } static void verifySegmentLimit(File outFile) { diff --git a/test/tools/pack200/Pack200Test.java b/test/tools/pack200/Pack200Test.java index d897bf8682491d12d56a7c8a3526717d05277a31..b4226f2845a4e986c64499318a11e555369a2bae 100644 --- a/test/tools/pack200/Pack200Test.java +++ b/test/tools/pack200/Pack200Test.java @@ -66,7 +66,7 @@ public class Pack200Test { } } - private static void doPackUnpack() { + private static void doPackUnpack() throws IOException { for (File in : jarList) { JarOutputStream javaUnpackerStream = null; JarOutputStream nativeUnpackerStream = null; @@ -117,12 +117,13 @@ public class Pack200Test { Utils.close((Closeable) jarFile); } } + Utils.cleanup(); // cleanup artifacts, if successful run } /** * @param args the command line arguments */ - public static void main(String[] args) { + public static void main(String[] args) throws IOException { // select the jars carefully, adding more jars will increase the // testing time, especially for jprt. jarList.add(Utils.locateJar("tools.jar")); diff --git a/test/tools/pack200/PackageVersionTest.java b/test/tools/pack200/PackageVersionTest.java index fe6d5d9cb596d8a7040ae29300d3afb77934721e..dc109b8a39188d508715a89847cb4446fd812216 100644 --- a/test/tools/pack200/PackageVersionTest.java +++ b/test/tools/pack200/PackageVersionTest.java @@ -1,6 +1,5 @@ - /* - * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -54,7 +53,7 @@ public class PackageVersionTest { public final static int JAVA7_PACKAGE_MAJOR_VERSION = 170; public final static int JAVA7_PACKAGE_MINOR_VERSION = 1; - public static void main(String... args) { + public static void main(String... args) throws IOException { if (!javaHome.getName().endsWith("jre")) { throw new RuntimeException("Error: requires an SDK to run"); } @@ -78,6 +77,7 @@ public class PackageVersionTest { // test for resource file, ie. no class files verifyPack("Test6.java", JAVA5_PACKAGE_MAJOR_VERSION, JAVA5_PACKAGE_MINOR_VERSION); + Utils.cleanup(); } static void verify6991164() { diff --git a/test/tools/pack200/RepackTest.java b/test/tools/pack200/RepackTest.java index 774e0d66eb3141201364bfc32c76c4fef0136187..8a3d86e7c94c12fc7163c547d7daccc84d589d3e 100644 --- a/test/tools/pack200/RepackTest.java +++ b/test/tools/pack200/RepackTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -36,6 +36,7 @@ public class RepackTest { public static void main(String... args) throws Exception { testRepack(); + Utils.cleanup(); } /* diff --git a/test/tools/pack200/T7007157.java b/test/tools/pack200/T7007157.java index 72f8792c22b6888a24d950f36d3d247e834abf52..29a8ca7fc1faac2b23c23bb73982a18ec87e982f 100644 --- a/test/tools/pack200/T7007157.java +++ b/test/tools/pack200/T7007157.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -63,5 +63,6 @@ public class T7007157 { Utils.close(fos); Utils.close(jarFile); } + Utils.cleanup(); } } diff --git a/test/tools/pack200/TestExceptions.java b/test/tools/pack200/TestExceptions.java index 0778cf5c7e17fe4a837b9acd4a91e045871cf9c9..1b6f39a78cc8e56b17f2f0ee7a2a76af8af7ea38 100644 --- a/test/tools/pack200/TestExceptions.java +++ b/test/tools/pack200/TestExceptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -219,12 +219,13 @@ public class TestExceptions { } } - public static void main(String... args) { + public static void main(String... args) throws IOException { init(); pack200Test1(); pack200Test2(); pack200Test3(); unpack200Test1(); + Utils.cleanup(); } // containers for test inputs and management diff --git a/test/tools/pack200/TimeStamp.java b/test/tools/pack200/TimeStamp.java index 8dfc2d9bc2ea5fe13323b0b1ee86709a859b8ef2..acce4624aab82c026d4a30f64975b1a9dc210ca6 100644 --- a/test/tools/pack200/TimeStamp.java +++ b/test/tools/pack200/TimeStamp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -149,6 +149,7 @@ public class TimeStamp { Utils.close(jf1); Utils.close(jf2); } + Utils.cleanup(); if (errors > 0) { throw new RuntimeException("FAIL:" + errors + " error(s) encounted"); } diff --git a/test/tools/pack200/UnpackerMemoryTest.java b/test/tools/pack200/UnpackerMemoryTest.java index fc63d154f9061e68738a9d09383aecbe9808e3fb..ec8ffb6a402aa17c3cff8bf324d135c26da6792d 100644 --- a/test/tools/pack200/UnpackerMemoryTest.java +++ b/test/tools/pack200/UnpackerMemoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -81,6 +81,7 @@ public class UnpackerMemoryTest { Utils.close(fos); } } + Utils.cleanup(); } } diff --git a/test/tools/pack200/Utils.java b/test/tools/pack200/Utils.java index df3426431d4d56974830f2f015179272aa1f9eb1..b1bc2f894b1cebaf01d408f2f37ef8aa515f9186 100644 --- a/test/tools/pack200/Utils.java +++ b/test/tools/pack200/Utils.java @@ -75,6 +75,7 @@ class Utils { static final File TEST_CLS_DIR = new File(System.getProperty("test.classes")); static final String VERIFIER_DIR_NAME = "pack200-verifier"; static final File VerifierJar = new File(VERIFIER_DIR_NAME + JAR_FILE_EXT); + static final File XCLASSES = new File("xclasses"); private Utils() {} // all static @@ -95,8 +96,7 @@ class Utils { } List javaFileList = findFiles(srcDir, createFilter(JAVA_FILE_EXT)); File tmpFile = File.createTempFile("javac", ".tmp"); - File classesDir = new File("xclasses"); - classesDir.mkdirs(); + XCLASSES.mkdirs(); FileOutputStream fos = null; PrintStream ps = null; try { @@ -111,14 +111,14 @@ class Utils { } compiler("-d", - "xclasses", + XCLASSES.getName(), "@" + tmpFile.getAbsolutePath()); jar("cvfe", VerifierJar.getName(), "sun.tools.pack.verify.Main", "-C", - "xclasses", + XCLASSES.getName(), "."); } @@ -175,6 +175,33 @@ class Utils { }; } + /* + * clean up all the usual suspects + */ + static void cleanup() throws IOException { + recursiveDelete(XCLASSES); + List toDelete = new ArrayList<>(); + toDelete.addAll(Utils.findFiles(new File("."), + Utils.createFilter(".out"))); + toDelete.addAll(Utils.findFiles(new File("."), + Utils.createFilter(".bak"))); + toDelete.addAll(Utils.findFiles(new File("."), + Utils.createFilter(".jar"))); + toDelete.addAll(Utils.findFiles(new File("."), + Utils.createFilter(".pack"))); + toDelete.addAll(Utils.findFiles(new File("."), + Utils.createFilter(".bnd"))); + toDelete.addAll(Utils.findFiles(new File("."), + Utils.createFilter(".txt"))); + toDelete.addAll(Utils.findFiles(new File("."), + Utils.createFilter(".idx"))); + toDelete.addAll(Utils.findFiles(new File("."), + Utils.createFilter(".gidx"))); + for (File f : toDelete) { + f.delete(); + } + } + static final FileFilter DIR_FILTER = new FileFilter() { public boolean accept(File pathname) { if (pathname.isDirectory()) { @@ -199,6 +226,9 @@ class Utils { Files.createDirectories(parent); } Files.copy(src.toPath(), dst.toPath(), COPY_ATTRIBUTES, REPLACE_EXISTING); + if (dst.isDirectory() && !dst.canWrite()) { + dst.setWritable(true); + } } static String baseName(File file, String extension) { diff --git a/test/tools/pack200/typeannos/TestTypeAnnotations.java b/test/tools/pack200/typeannos/TestTypeAnnotations.java index 598185aaabc783ba4f1128f8c7e99a059f7875ad..f00801b5b14c5fa6956fbc1fe24bb357fb4b330b 100644 --- a/test/tools/pack200/typeannos/TestTypeAnnotations.java +++ b/test/tools/pack200/typeannos/TestTypeAnnotations.java @@ -41,5 +41,6 @@ public class TestTypeAnnotations { "-C", Utils.TEST_CLS_DIR.getAbsolutePath(), "."); Utils.testWithRepack(testFile, "--unknown-attribute=error"); + Utils.cleanup(); } }