--- old/src/java.base/share/classes/java/lang/Class.java 2016-10-16 20:00:04.944483222 +0200 +++ new/src/java.base/share/classes/java/lang/Class.java 2016-10-16 20:00:04.829485201 +0200 @@ -557,7 +557,7 @@ Class caller = Reflection.getCallerClass(); if (newInstanceCallerCache != caller) { int modifiers = tmpConstructor.getModifiers(); - Reflection.ensureMemberAccess(caller, this, null, modifiers); + Reflection.ensureMemberAccess(caller, this, this, modifiers); newInstanceCallerCache = caller; } // Run constructor --- old/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java 2016-10-16 20:00:05.263477733 +0200 +++ new/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java 2016-10-16 20:00:05.178479195 +0200 @@ -312,22 +312,22 @@ // (See also Class.newInstance(), which uses a similar method.) // // A more complicated security check cache is needed for Method and Field - // The cache can be either null (empty cache), a 2-array of {caller,target}, - // or a caller (with target implicitly equal to this.clazz). - // In the 2-array case, the target is always different from the clazz. + // The cache can be either null (empty cache), a 2-array of {caller,targetClass}, + // or a caller (with targetClass implicitly equal to memberClass). + // In the 2-array case, the targetClass is always different from the memberClass. volatile Object securityCheckCache; - void checkAccess(Class caller, Class clazz, Object obj, int modifiers) + final void checkAccess(Class caller, Class memberClass, + Class targetClass, int modifiers) throws IllegalAccessException { - if (caller == clazz) { // quick check + if (caller == memberClass) { // quick check return; // ACCESS IS OK } Object cache = securityCheckCache; // read volatile - Class targetClass = clazz; - if (obj != null + if (targetClass != null // instance member or constructor && Modifier.isProtected(modifiers) - && ((targetClass = obj.getClass()) != clazz)) { + && targetClass != memberClass) { // Must match a 2-list of { caller, targetClass }. if (cache instanceof Class[]) { Class[] cache2 = (Class[]) cache; @@ -339,25 +339,27 @@ // subsumes range check for [0].) } } else if (cache == caller) { - // Non-protected case (or obj.class == this.clazz). + // Non-protected case (or targetClass == memberClass or static member). return; // ACCESS IS OK } // If no return, fall through to the slow path. - slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass); + slowCheckMemberAccess(caller, memberClass, targetClass, modifiers); } // Keep all this slow stuff out of line: - void slowCheckMemberAccess(Class caller, Class clazz, Object obj, int modifiers, - Class targetClass) + void slowCheckMemberAccess(Class caller, Class memberClass, + Class targetClass, int modifiers) throws IllegalAccessException { - Reflection.ensureMemberAccess(caller, clazz, obj, modifiers); + Reflection.ensureMemberAccess(caller, memberClass, targetClass, modifiers); // Success: Update the cache. - Object cache = ((targetClass == clazz) - ? caller - : new Class[] { caller, targetClass }); + Object cache = (targetClass != null + && Modifier.isProtected(modifiers) + && targetClass != memberClass) + ? new Class[] { caller, targetClass } + : caller; // Note: The two cache elements are not volatile, // but they are effectively final. The Java memory model --- old/src/java.base/share/classes/java/lang/reflect/Constructor.java 2016-10-16 20:00:05.521473294 +0200 +++ new/src/java.base/share/classes/java/lang/reflect/Constructor.java 2016-10-16 20:00:05.409475221 +0200 @@ -443,7 +443,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, null, modifiers); + checkAccess(caller, clazz, clazz, modifiers); } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); --- old/src/java.base/share/classes/java/lang/reflect/Field.java 2016-10-16 20:00:05.837467857 +0200 +++ new/src/java.base/share/classes/java/lang/reflect/Field.java 2016-10-16 20:00:05.728469732 +0200 @@ -403,7 +403,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } return getFieldAccessor(obj).get(obj); } @@ -437,7 +437,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } return getFieldAccessor(obj).getBoolean(obj); } @@ -471,7 +471,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } return getFieldAccessor(obj).getByte(obj); } @@ -507,7 +507,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } return getFieldAccessor(obj).getChar(obj); } @@ -543,7 +543,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } return getFieldAccessor(obj).getShort(obj); } @@ -579,7 +579,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } return getFieldAccessor(obj).getInt(obj); } @@ -615,7 +615,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } return getFieldAccessor(obj).getLong(obj); } @@ -651,7 +651,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } return getFieldAccessor(obj).getFloat(obj); } @@ -687,7 +687,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } return getFieldAccessor(obj).getDouble(obj); } @@ -765,7 +765,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } getFieldAccessor(obj).set(obj, value); } @@ -801,7 +801,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } getFieldAccessor(obj).setBoolean(obj, z); } @@ -837,7 +837,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } getFieldAccessor(obj).setByte(obj, b); } @@ -873,7 +873,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } getFieldAccessor(obj).setChar(obj, c); } @@ -909,7 +909,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } getFieldAccessor(obj).setShort(obj, s); } @@ -945,7 +945,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } getFieldAccessor(obj).setInt(obj, i); } @@ -981,7 +981,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } getFieldAccessor(obj).setLong(obj, l); } @@ -1017,7 +1017,7 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } getFieldAccessor(obj).setFloat(obj, f); } @@ -1053,11 +1053,20 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, obj); } getFieldAccessor(obj).setDouble(obj, d); } + // check access to field + private void checkAccess(Class caller, Object obj) + throws IllegalAccessException + { + checkAccess(caller, clazz, + Modifier.isStatic(modifiers) ? null : obj.getClass(), + modifiers); + } + // security check is done before calling this method private FieldAccessor getFieldAccessor(Object obj) throws IllegalAccessException --- old/src/java.base/share/classes/java/lang/reflect/Method.java 2016-10-16 20:00:06.092463469 +0200 +++ new/src/java.base/share/classes/java/lang/reflect/Method.java 2016-10-16 20:00:05.994465155 +0200 @@ -526,7 +526,9 @@ { if (!override) { Class caller = Reflection.getCallerClass(); - checkAccess(caller, clazz, obj, modifiers); + checkAccess(caller, clazz, + Modifier.isStatic(modifiers) ? null : obj.getClass(), + modifiers); } MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { --- old/src/java.base/share/classes/jdk/internal/reflect/Reflection.java 2016-10-16 20:00:06.422457791 +0200 +++ new/src/java.base/share/classes/jdk/internal/reflect/Reflection.java 2016-10-16 20:00:06.305459804 +0200 @@ -84,9 +84,22 @@ public static native int getClassAccessFlags(Class c); + /** + * Ensures that access to a member is granted and throws + * IllegalAccessException if not. + * + * @param currentClass the class performing the access + * @param memberClass the declaring class of the member being accessed + * @param targetClass the class of target object if accessing instance + * field or method; + * or the declaring class if accessing constructor; + * or null if accessing static field or method + * @param modifiers the member's access modifiers + * @throws IllegalAccessException if access to member is denied + */ public static void ensureMemberAccess(Class currentClass, Class memberClass, - Object target, + Class targetClass, int modifiers) throws IllegalAccessException { @@ -94,18 +107,15 @@ throw new InternalError(); } - if (!verifyMemberAccess(currentClass, memberClass, target, modifiers)) { - throwIllegalAccessException(currentClass, memberClass, target, modifiers); + if (!verifyMemberAccess(currentClass, memberClass, targetClass, modifiers)) { + throwIllegalAccessException(currentClass, memberClass, targetClass, modifiers); } } - public static boolean verifyMemberAccess(Class currentClass, - // Declaring class of field - // or method - Class memberClass, - // May be NULL in case of statics - Object target, - int modifiers) + private static boolean verifyMemberAccess(Class currentClass, + Class memberClass, + Class targetClass, + int modifiers) { // Verify that currentClass can access a field, method, or // constructor of memberClass, where that member's access bits are @@ -162,18 +172,18 @@ return false; } - if (Modifier.isProtected(modifiers)) { - // Additional test for protected members: JLS 6.6.2 - Class targetClass = (target == null ? memberClass : target.getClass()); - if (targetClass != currentClass) { - if (!gotIsSameClassPackage) { - isSameClassPackage = isSameClassPackage(currentClass, memberClass); - gotIsSameClassPackage = true; - } - if (!isSameClassPackage) { - if (!isSubclassOf(targetClass, currentClass)) { - return false; - } + // Additional test for protected instance members + // and protected constructors: JLS 6.6.2 + if (targetClass != null && Modifier.isProtected(modifiers) && + targetClass != currentClass) + { + if (!gotIsSameClassPackage) { + isSameClassPackage = isSameClassPackage(currentClass, memberClass); + gotIsSameClassPackage = true; + } + if (!isSameClassPackage) { + if (!isSubclassOf(targetClass, currentClass)) { + return false; } } } --- old/src/java.base/share/classes/sun/reflect/misc/ReflectUtil.java 2016-10-16 20:00:06.678453386 +0200 +++ new/src/java.base/share/classes/sun/reflect/misc/ReflectUtil.java 2016-10-16 20:00:06.586454969 +0200 @@ -44,9 +44,21 @@ return Class.forName(name); } - /* - * Reflection.ensureMemberAccess is overly-restrictive - * due to a bug. We awkwardly work around it for now. + /** + * Ensures that access to a method or field is granted and throws + * IllegalAccessException if not. This method is not suitable for checking + * access to constructors. + * + * @param currentClass the class performing the access + * @param memberClass the declaring class of the member being accessed + * @param target the target object if accessing instance field or method; + * or null if accessing static field or method or if target + * object access rights will be checked later + * @param modifiers the member's access modifiers + * @throws IllegalAccessException if access to member is denied + * @implNote Delegates directly to + * {@link Reflection#ensureMemberAccess(Class, Class, Class, int)} + * which should be used instead. */ public static void ensureMemberAccess(Class currentClass, Class memberClass, @@ -54,62 +66,10 @@ int modifiers) throws IllegalAccessException { - if (target == null && Modifier.isProtected(modifiers)) { - int mods = modifiers; - mods = mods & (~Modifier.PROTECTED); - mods = mods | Modifier.PUBLIC; - - /* - * See if we fail because of class modifiers - */ - Reflection.ensureMemberAccess(currentClass, - memberClass, - target, - mods); - try { - /* - * We're still here so class access was ok. - * Now try with default field access. - */ - mods = mods & (~Modifier.PUBLIC); - Reflection.ensureMemberAccess(currentClass, - memberClass, - target, - mods); - /* - * We're still here so access is ok without - * checking for protected. - */ - return; - } catch (IllegalAccessException e) { - /* - * Access failed but we're 'protected' so - * if the test below succeeds then we're ok. - */ - if (isSubclassOf(currentClass, memberClass)) { - return; - } else { - throw e; - } - } - } else { - Reflection.ensureMemberAccess(currentClass, - memberClass, - target, - modifiers); - } - } - - private static boolean isSubclassOf(Class queryClass, - Class ofClass) - { - while (queryClass != null) { - if (queryClass == ofClass) { - return true; - } - queryClass = queryClass.getSuperclass(); - } - return false; + Reflection.ensureMemberAccess(currentClass, + memberClass, + target == null ? null : target.getClass(), + modifiers); } /** --- /dev/null 2016-10-16 17:22:14.379284385 +0200 +++ new/test/java/lang/reflect/AccessControl/AccessControlTest.java 2016-10-16 20:00:06.814451046 +0200 @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2016, 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 member.MemberFactory; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toCollection; +import static member.MemberFactory.*; +import static member.MemberFactory.Group.*; + +/** + * @test + * @summary An exhaustive test of reflective access controls + * @bug 6378384 + * @build a.PublicSuper a.Package b.PublicSub b.Package member.MemberFactory + * @run main AccessControlTest + */ +public class AccessControlTest { + + public static void main(String[] args) throws Exception { + boolean ok = true; + + // use this for generating an exhaustive set of test cases on stdout: + +// String[] classNames = new String[]{ +// "a.PublicSuper", "a.Package", "b.PublicSub", "b.Package" +// }; +// for (String current : classNames) { +// for (String member : classNames) { +// for (String target : classNames) { +// if (Class.forName(member).isAssignableFrom(Class.forName(target))) { +// ok &= new Test() +// .current(current).member(member).target(target) +// .allowed(MemberFactory.values()) +// .perform(true); +// } +// } +// } +// } + + ok &= new Test() + .current("a.PublicSuper").member("a.PublicSuper").target("a.PublicSuper") + .allowedAll() + .perform(); + ok &= new Test() + .current("a.PublicSuper").member("a.PublicSuper").target("b.PublicSub") + .allowedAll() + .perform(); + ok &= new Test() + .current("a.PublicSuper").member("a.Package").target("a.Package") + .allowed(PACKAGE_INSTANCE_F_M, PROTECTED_INSTANCE_F_M, PUBLIC_INSTANCE_F_M, + PACKAGE_STATIC_F_M, PROTECTED_STATIC_F_M, PUBLIC_STATIC_F_M, + PACKAGE_C, PROTECTED_C, PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PRIVATE_STATIC_F_M, PRIVATE_C) + .perform(); + ok &= new Test() + .current("a.PublicSuper").member("b.PublicSub").target("b.PublicSub") + .allowed(PUBLIC_INSTANCE_F_M, PUBLIC_STATIC_F_M, PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PACKAGE_INSTANCE_F_M, PROTECTED_INSTANCE_F_M, + PRIVATE_STATIC_F_M, PACKAGE_STATIC_F_M, PROTECTED_STATIC_F_M, + PRIVATE_C, PACKAGE_C, PROTECTED_C) + .perform(); + ok &= new Test() + .current("a.PublicSuper").member("b.Package").target("b.Package") + .deniedAll() + .perform(); + ok &= new Test() + .current("a.Package").member("a.PublicSuper").target("a.PublicSuper") + .allowed(PACKAGE_INSTANCE_F_M, PROTECTED_INSTANCE_F_M, PUBLIC_INSTANCE_F_M, + PACKAGE_STATIC_F_M, PROTECTED_STATIC_F_M, PUBLIC_STATIC_F_M, + PACKAGE_C, PROTECTED_C, PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PRIVATE_STATIC_F_M, PRIVATE_C) + .perform(); + ok &= new Test() + .current("a.Package").member("a.PublicSuper").target("b.PublicSub") + .allowed(PACKAGE_INSTANCE_F_M, PROTECTED_INSTANCE_F_M, PUBLIC_INSTANCE_F_M, + PACKAGE_STATIC_F_M, PROTECTED_STATIC_F_M, PUBLIC_STATIC_F_M, + PACKAGE_C, PROTECTED_C, PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PRIVATE_STATIC_F_M, PRIVATE_C) + .perform(); + ok &= new Test() + .current("a.Package").member("a.Package").target("a.Package") + .allowedAll() + .perform(); + ok &= new Test() + .current("a.Package").member("b.PublicSub").target("b.PublicSub") + .allowed(PUBLIC_INSTANCE_F_M, PUBLIC_STATIC_F_M, PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PACKAGE_INSTANCE_F_M, PROTECTED_INSTANCE_F_M, + PRIVATE_STATIC_F_M, PACKAGE_STATIC_F_M, PROTECTED_STATIC_F_M, + PRIVATE_C, PACKAGE_C, PROTECTED_C) + .perform(); + ok &= new Test() + .current("a.Package").member("b.Package").target("b.Package") + .deniedAll() + .perform(); + ok &= new Test() + .current("b.PublicSub").member("a.PublicSuper").target("a.PublicSuper") + .allowed(PUBLIC_INSTANCE_F_M, PROTECTED_STATIC_F_M, PUBLIC_STATIC_F_M, + PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PACKAGE_INSTANCE_F_M, PROTECTED_INSTANCE_F_M, + PRIVATE_STATIC_F_M, PACKAGE_STATIC_F_M, PRIVATE_C, + PACKAGE_C, PROTECTED_C) + .perform(); + ok &= new Test() + .current("b.PublicSub").member("a.PublicSuper").target("b.PublicSub") + .allowed(PROTECTED_INSTANCE_F_M, PUBLIC_INSTANCE_F_M, PROTECTED_STATIC_F_M, + PUBLIC_STATIC_F_M, PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PACKAGE_INSTANCE_F_M, PRIVATE_STATIC_F_M, + PACKAGE_STATIC_F_M, PRIVATE_C, PACKAGE_C, + PROTECTED_C) + .perform(); + ok &= new Test() + .current("b.PublicSub").member("a.Package").target("a.Package") + .deniedAll() + .perform(); + ok &= new Test() + .current("b.PublicSub").member("b.PublicSub").target("b.PublicSub") + .allowedAll() + .perform(); + ok &= new Test() + .current("b.PublicSub").member("b.Package").target("b.Package") + .allowed(PACKAGE_INSTANCE_F_M, PROTECTED_INSTANCE_F_M, PUBLIC_INSTANCE_F_M, + PACKAGE_STATIC_F_M, PROTECTED_STATIC_F_M, PUBLIC_STATIC_F_M, + PACKAGE_C, PROTECTED_C, PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PRIVATE_STATIC_F_M, PRIVATE_C) + .perform(); + ok &= new Test() + .current("b.Package").member("a.PublicSuper").target("a.PublicSuper") + .allowed(PUBLIC_INSTANCE_F_M, PUBLIC_STATIC_F_M, PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PACKAGE_INSTANCE_F_M, PROTECTED_INSTANCE_F_M, + PRIVATE_STATIC_F_M, PACKAGE_STATIC_F_M, PROTECTED_STATIC_F_M, + PRIVATE_C, PACKAGE_C, PROTECTED_C) + .perform(); + ok &= new Test() + .current("b.Package").member("a.PublicSuper").target("b.PublicSub") + .allowed(PUBLIC_INSTANCE_F_M, PUBLIC_STATIC_F_M, PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PACKAGE_INSTANCE_F_M, PROTECTED_INSTANCE_F_M, + PRIVATE_STATIC_F_M, PACKAGE_STATIC_F_M, PROTECTED_STATIC_F_M, + PRIVATE_C, PACKAGE_C, PROTECTED_C) + .perform(); + ok &= new Test() + .current("b.Package").member("a.Package").target("a.Package") + .deniedAll() + .perform(); + ok &= new Test() + .current("b.Package").member("b.PublicSub").target("b.PublicSub") + .allowed(PACKAGE_INSTANCE_F_M, PROTECTED_INSTANCE_F_M, PUBLIC_INSTANCE_F_M, + PACKAGE_STATIC_F_M, PROTECTED_STATIC_F_M, PUBLIC_STATIC_F_M, + PACKAGE_C, PROTECTED_C, PUBLIC_C) + .denied (PRIVATE_INSTANCE_F_M, PRIVATE_STATIC_F_M, PRIVATE_C) + .perform(); + ok &= new Test() + .current("b.Package").member("b.Package").target("b.Package") + .allowedAll() + .perform(); + + if (ok) { + System.err.println("\nAll cases passed."); + } else { + System.err.println(); + throw new RuntimeException("Some cases failed - see log."); + } + } + + static class Test { + + Class currentClass, memberClass, targetClass; + EnumSet expectAllowedMembers = EnumSet.noneOf(MemberFactory.class); + EnumSet expectDeniedMembers = EnumSet.noneOf(MemberFactory.class); + + Test current(String current) { + try { + currentClass = Class.forName(current); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + return this; + } + + Test member(String member) { + try { + memberClass = Class.forName(member); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + return this; + } + + Test target(String target) { + try { + targetClass = Class.forName(target); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + return this; + } + + Test allowed(MemberFactory... allowed) { + expectAllowedMembers = MemberFactory.asSet(allowed); + return this; + } + + Test allowed(MemberFactory.Group... allowedGroups) { + expectAllowedMembers = MemberFactory.groupsToMembers( + MemberFactory.Group.asSet(allowedGroups)); + return this; + } + + Test allowedAll() { + expectAllowedMembers = EnumSet.allOf(MemberFactory.class); + return this; + } + + Test denied(MemberFactory... denied) { + expectDeniedMembers = MemberFactory.asSet(denied); + return this; + } + + Test denied(MemberFactory.Group... deniedGroups) { + expectDeniedMembers = MemberFactory.groupsToMembers( + MemberFactory.Group.asSet(deniedGroups)); + return this; + } + + Test deniedAll() { + expectDeniedMembers = EnumSet.allOf(MemberFactory.class); + return this; + } + + boolean perform() { + return perform(false); + } + + boolean perform(boolean printCases) { + + // some validation 1st + EnumSet intersection = EnumSet.copyOf(expectAllowedMembers); + intersection.retainAll(expectDeniedMembers); + if (!intersection.isEmpty()) { + throw new IllegalArgumentException( + "Expected allowed and denied MemberFactories have non-empty intersection: " + + intersection); + } + + EnumSet missing = EnumSet.allOf(MemberFactory.class); + missing.removeAll(expectAllowedMembers); + missing.removeAll(expectDeniedMembers); + if (!missing.isEmpty()) { + throw new IllegalArgumentException( + "Union of expected allowed and denied MemberFactories is missing elements: " + + missing); + } + + // retrieve method that will perform reflective access + Method checkAccessMethod; + try { + checkAccessMethod = currentClass.getDeclaredMethod( + "checkAccess", AccessibleObject.class, Object.class); + // in case of inaccessible currentClass + checkAccessMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + + // construct a target object (for instance field/method) + Object target; + if (targetClass == null) { + target = null; + } else { + Constructor targetConstructor = + (Constructor) PUBLIC_CONSTRUCTOR.apply(targetClass); + // in case of inaccessible targetClass + targetConstructor.setAccessible(true); + try { + target = targetConstructor.newInstance( + new Object[targetConstructor.getParameterCount()]); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + System.err.println(); + + Map> actualMembers = Stream.concat( + + expectAllowedMembers.stream().map(member -> new Trial(member, true)), + expectDeniedMembers.stream().map(member -> new Trial(member, false)) + + ).map(trial -> { + + // obtain AccessibleObject to be used to perform reflective access + AccessibleObject accessibleObject = trial.member.apply(memberClass); + + // only need target 'obj' for instance fields and methods + Object obj = + (accessibleObject instanceof Field && + !Modifier.isStatic(((Field) accessibleObject).getModifiers()) + || + accessibleObject instanceof Method && + !Modifier.isStatic(((Method) accessibleObject).getModifiers()) + ) + ? target : null; + + // invoke checkAccess method and let it perform the reflective access + try { + checkAccessMethod.invoke(null, accessibleObject, obj); + trial.actualAllowed = true; + } catch (IllegalAccessException e) { + // should not happen as checkAccessMethod.isAccessible() + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof IllegalAccessException) { + trial.actualAllowed = false; + } else { + // any other Exception is a fault in test or infrastructure - fail fast + throw new RuntimeException(e.getTargetException()); + } + } + + System.err.printf( + "%-18s accessing %18s's %-25s %-36s - expected %s, actual %s: %s\n", + currentClass.getName(), memberClass.getName(), trial.member.name(), + (obj == null ? "" : "with instance of " + targetClass.getName()), + (trial.expectAllowed ? "allowed" : "denied "), + (trial.actualAllowed ? "allowed" : "denied "), + (trial.expectAllowed == trial.actualAllowed ? "OK" : "FAILURE") + ); + + return trial; + + }).collect( + groupingBy( + Trial::isActualAllowed, + mapping( + Trial::getMember, + toCollection(() -> EnumSet.noneOf(MemberFactory.class)))) + ); + + EnumSet actualAllowedMembers = + Optional.ofNullable(actualMembers.get(true)) + .orElse(EnumSet.noneOf(MemberFactory.class)); + EnumSet actualDeniedMembers = + Optional.ofNullable(actualMembers.get(false)) + .orElse(EnumSet.noneOf(MemberFactory.class)); + + if (printCases) { + System.out.printf( + " ok &= new Test()\n" + + " .current(\"%s\").member(\"%s\").target(\"%s\")\n", + currentClass.getName(), memberClass.getName(), targetClass.getName() + ); + + if (actualAllowedMembers.equals(EnumSet.allOf(MemberFactory.class))) { + System.out.print(" .allowedAll()\n"); + } else if (!actualAllowedMembers.isEmpty()) { + EnumSet actualAllowed = + MemberFactory.membersToGroupsOrNull(actualAllowedMembers); + if (actualAllowed == null) + actualAllowed = actualAllowedMembers; + System.out.print( + chunkBy(3, actualAllowed.stream().map(Enum::name)) + .map(chunk -> chunk.collect(joining(", "))) + .collect(joining(",\n" + + " ", + " .allowed(", + ")\n")) + ); + } + + if (actualDeniedMembers.equals(EnumSet.allOf(MemberFactory.class))) { + System.out.print(" .deniedAll()\n"); + } else if (!actualDeniedMembers.isEmpty()) { + EnumSet actualDenied = + MemberFactory.membersToGroupsOrNull(actualDeniedMembers); + if (actualDenied == null) + actualDenied = actualAllowedMembers; + System.out.print( + chunkBy(3, actualDenied.stream().map(Enum::name)) + .map(chunk -> chunk.collect(joining(", "))) + .collect(joining(",\n" + + " ", + " .denied (", + ")\n")) + ); + } + + System.out.print( + " .perform();\n" + ); + } + + return expectAllowedMembers.equals(actualAllowedMembers) && + expectDeniedMembers.equals(actualDeniedMembers); + } + } + + static Stream> chunkBy(int chunkSize, Stream stream) { + Iterator elements = stream.iterator(); + Stream.Builder> b1 = Stream.builder(); + while (elements.hasNext()) { + Stream.Builder b2 = Stream.builder(); + for (int i = 0; i < chunkSize && elements.hasNext(); i++) { + b2.accept(elements.next()); + } + b1.accept(b2.build()); + } + return b1.build(); + } + + static class Trial { + final MemberFactory member; + final boolean expectAllowed; + boolean actualAllowed; + + Trial(MemberFactory member, boolean expectAllowed) { + this.member = member; + this.expectAllowed = expectAllowed; + } + + MemberFactory getMember() { + return member; + } + + boolean isActualAllowed() { + return actualAllowed; + } + } +} --- /dev/null 2016-10-16 17:22:14.379284385 +0200 +++ new/test/java/lang/reflect/AccessControl/a/Package.java 2016-10-16 20:00:07.079446486 +0200 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016, 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 a; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * A package-private class in a. + */ +class Package { + + // fields + private static int privateStatic; + private int privateInstance; + static int packageStatic; + int packageInstance; + protected static int protectedStatic; + protected int protectedInstance; + public static int publicStatic; + public int publicInstance; + + // methods + private static int privateStatic() { return 42; } + private int privateInstance() { return 42; } + static int packageStatic() { return 42; } + int packageInstance() { return 42; } + protected static int protectedStatic() { return 42; } + protected int protectedInstance() { return 42; } + public static int publicStatic() { return 42; } + public int publicInstance() { return 42; } + + // constructors + private Package(Void _1, Void _2, Void _3) {} + Package(Void _1, Void _2) {} + protected Package(Void _1) {} + public Package() {} + + + // testing method + public static void checkAccess(AccessibleObject accessibleObject, Object obj) + throws IllegalAccessException, + InvocationTargetException, + InstantiationException + { + if (accessibleObject instanceof Field) { + Field field = (Field) accessibleObject; + field.set(obj, 42); + field.get(obj); + } else if (accessibleObject instanceof Method) { + Method method = (Method) accessibleObject; + method.invoke(obj); + } else if (accessibleObject instanceof Constructor) { + Constructor constructor = (Constructor) accessibleObject; + Object[] params = new Object[constructor.getParameterCount()]; + constructor.newInstance(params); + } + } +} --- /dev/null 2016-10-16 17:22:14.379284385 +0200 +++ new/test/java/lang/reflect/AccessControl/a/PublicSuper.java 2016-10-16 20:00:07.373441428 +0200 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016, 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 a; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * A public class in a which is a superclass of public class in b. + */ +public class PublicSuper { + + // fields + private static int privateStatic; + private int privateInstance; + static int packageStatic; + int packageInstance; + protected static int protectedStatic; + protected int protectedInstance; + public static int publicStatic; + public int publicInstance; + + // methods + private static int privateStatic() { return 42; } + private int privateInstance() { return 42; } + static int packageStatic() { return 42; } + int packageInstance() { return 42; } + protected static int protectedStatic() { return 42; } + protected int protectedInstance() { return 42; } + public static int publicStatic() { return 42; } + public int publicInstance() { return 42; } + + // constructors + private PublicSuper(Void _1, Void _2, Void _3) {} + PublicSuper(Void _1, Void _2) {} + protected PublicSuper(Void _1) {} + public PublicSuper() {} + + + // testing method + public static void checkAccess(AccessibleObject accessibleObject, Object obj) + throws IllegalAccessException, + InvocationTargetException, + InstantiationException + { + if (accessibleObject instanceof Field) { + Field field = (Field) accessibleObject; + field.set(obj, 42); + field.get(obj); + } else if (accessibleObject instanceof Method) { + Method method = (Method) accessibleObject; + method.invoke(obj); + } else if (accessibleObject instanceof Constructor) { + Constructor constructor = (Constructor) accessibleObject; + Object[] params = new Object[constructor.getParameterCount()]; + constructor.newInstance(params); + } + } +} --- /dev/null 2016-10-16 17:22:14.379284385 +0200 +++ new/test/java/lang/reflect/AccessControl/b/Package.java 2016-10-16 20:00:07.714435560 +0200 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016, 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 b; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * A package-private class in b. + */ +class Package { + + // fields + private static int privateStatic; + private int privateInstance; + static int packageStatic; + int packageInstance; + protected static int protectedStatic; + protected int protectedInstance; + public static int publicStatic; + public int publicInstance; + + // methods + private static int privateStatic() { return 42; } + private int privateInstance() { return 42; } + static int packageStatic() { return 42; } + int packageInstance() { return 42; } + protected static int protectedStatic() { return 42; } + protected int protectedInstance() { return 42; } + public static int publicStatic() { return 42; } + public int publicInstance() { return 42; } + + // constructors + private Package(Void _1, Void _2, Void _3) {} + Package(Void _1, Void _2) {} + protected Package(Void _1) {} + public Package() {} + + + // testing method + public static void checkAccess(AccessibleObject accessibleObject, Object obj) + throws IllegalAccessException, + InvocationTargetException, + InstantiationException + { + if (accessibleObject instanceof Field) { + Field field = (Field) accessibleObject; + field.set(obj, 42); + field.get(obj); + } else if (accessibleObject instanceof Method) { + Method method = (Method) accessibleObject; + method.invoke(obj); + } else if (accessibleObject instanceof Constructor) { + Constructor constructor = (Constructor) accessibleObject; + Object[] params = new Object[constructor.getParameterCount()]; + constructor.newInstance(params); + } + } +} --- /dev/null 2016-10-16 17:22:14.379284385 +0200 +++ new/test/java/lang/reflect/AccessControl/b/PublicSub.java 2016-10-16 20:00:08.010430467 +0200 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016, 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 b; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * A public class in b which is a subclass of public class in a. + */ +public class PublicSub extends a.PublicSuper { + + // fields + private static int privateStatic; + private int privateInstance; + static int packageStatic; + int packageInstance; + protected static int protectedStatic; + protected int protectedInstance; + public static int publicStatic; + public int publicInstance; + + // methods + private static int privateStatic() { return 42; } + private int privateInstance() { return 42; } + static int packageStatic() { return 42; } + int packageInstance() { return 42; } + protected static int protectedStatic() { return 42; } + protected int protectedInstance() { return 42; } + public static int publicStatic() { return 42; } + public int publicInstance() { return 42; } + + // constructors + private PublicSub(Void _1, Void _2, Void _3) {} + PublicSub(Void _1, Void _2) {} + protected PublicSub(Void _1) {} + public PublicSub() {} + + + // testing method + public static void checkAccess(AccessibleObject accessibleObject, Object obj) + throws IllegalAccessException, + InvocationTargetException, + InstantiationException + { + if (accessibleObject instanceof Field) { + Field field = (Field) accessibleObject; + field.set(obj, 42); + field.get(obj); + } else if (accessibleObject instanceof Method) { + Method method = (Method) accessibleObject; + method.invoke(obj); + } else if (accessibleObject instanceof Constructor) { + Constructor constructor = (Constructor) accessibleObject; + Object[] params = new Object[constructor.getParameterCount()]; + constructor.newInstance(params); + } + } +} --- /dev/null 2016-10-16 17:22:14.379284385 +0200 +++ new/test/java/lang/reflect/AccessControl/member/MemberFactory.java 2016-10-16 20:00:08.310425305 +0200 @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2016, 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 member; + +import java.lang.reflect.AccessibleObject; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.function.BiFunction; +import java.util.function.Function; + +import static member.MemberFactory.Kind.CONSTRUCTOR; +import static member.MemberFactory.Kind.FIELD; +import static member.MemberFactory.Kind.METHOD; + +/** + * Enumeration of: + *

+ * {private, package, protected, public} x {instance, static} x {field, method} + *

+ * and: + *

+ * {private, package, protected, public} x {constructor}, + *

+ * with each element acting as a factory of AccessibleObject(s) + * declared by given declaringClass(es). + */ +public enum MemberFactory implements Function, AccessibleObject> { + // instance fields + PRIVATE_INSTANCE_FIELD(FIELD, "privateInstance"), + PACKAGE_INSTANCE_FIELD(FIELD, "packageInstance"), + PROTECTED_INSTANCE_FIELD(FIELD, "protectedInstance"), + PUBLIC_INSTANCE_FIELD(FIELD, "publicInstance"), + // instance methods + PRIVATE_INSTANCE_METHOD(METHOD, "privateInstance"), + PACKAGE_INSTANCE_METHOD(METHOD, "packageInstance"), + PROTECTED_INSTANCE_METHOD(METHOD, "protectedInstance"), + PUBLIC_INSTANCE_METHOD(METHOD, "publicInstance"), + // static fields + PRIVATE_STATIC_FIELD(FIELD, "privateStatic"), + PACKAGE_STATIC_FIELD(FIELD, "packageStatic"), + PROTECTED_STATIC_FIELD(FIELD, "protectedStatic"), + PUBLIC_STATIC_FIELD(FIELD, "publicStatic"), + // static methods + PRIVATE_STATIC_METHOD(METHOD, "privateStatic"), + PACKAGE_STATIC_METHOD(METHOD, "packageStatic"), + PROTECTED_STATIC_METHOD(METHOD, "protectedStatic"), + PUBLIC_STATIC_METHOD(METHOD, "publicStatic"), + // constructors + PRIVATE_CONSTRUCTOR(CONSTRUCTOR, null, Void.class, Void.class, Void.class), + PACKAGE_CONSTRUCTOR(CONSTRUCTOR, null, Void.class, Void.class), + PROTECTED_CONSTRUCTOR(CONSTRUCTOR, null, Void.class), + PUBLIC_CONSTRUCTOR(CONSTRUCTOR, null),; + + final Kind kind; + final String name; + final Class[] parameterTypes; + + MemberFactory(Kind kind, String name, Class... parameterTypes) { + this.kind = kind; + this.name = name; + this.parameterTypes = parameterTypes; + } + + @Override + public AccessibleObject apply(Class declaringClass) { + return kind.apply(declaringClass, this); + } + + public static EnumSet asSet(MemberFactory... members) { + return members.length == 0 ? EnumSet.noneOf(MemberFactory.class) + : EnumSet.copyOf(Arrays.asList(members)); + } + + /** + * @param members the set of MemberFactory(s) to convert to set of + * MemberFactory.Group(s). + * @return a set of groups that cover all elements of the members set if + * such set of groups exists or null if it doesn't. + */ + public static EnumSet membersToGroupsOrNull(EnumSet members) { + EnumSet mSet = members.clone(); + EnumSet gSet = EnumSet.allOf(Group.class); + Iterator gIter = gSet.iterator(); + while (gIter.hasNext()) { + Group g = gIter.next(); + if (mSet.containsAll(g.members)) { + mSet.removeAll(g.members); + } else { + gIter.remove(); + } + } + return mSet.isEmpty() ? gSet : null; + } + + /** + * @param groups the set of MemberFactory.Group(s) to convert to set of + * MemberFactory(s). + * @return a set of members as a union of members of all groups. + */ + public static EnumSet groupsToMembers(EnumSet groups) { + EnumSet mSet = EnumSet.noneOf(MemberFactory.class); + for (Group g : groups) { + mSet.addAll(g.members); + } + return mSet; + } + + enum Kind implements BiFunction, MemberFactory, AccessibleObject> { + FIELD { + @Override + public AccessibleObject apply(Class declaringClass, MemberFactory factory) { + assert factory.kind == this; + try { + return declaringClass.getDeclaredField(factory.name); + } catch (NoSuchFieldException e) { + // a fault in test - fail fast + throw new RuntimeException(e.getMessage()); + } + } + }, + METHOD { + @Override + public AccessibleObject apply(Class declaringClass, MemberFactory factory) { + assert factory.kind == this; + try { + return declaringClass.getDeclaredMethod(factory.name, factory.parameterTypes); + } catch (NoSuchMethodException e) { + // a fault in test - fail fast + throw new RuntimeException(e.getMessage()); + } + } + }, + CONSTRUCTOR { + @Override + public AccessibleObject apply(Class declaringClass, MemberFactory factory) { + assert factory.kind == this; + try { + return declaringClass.getDeclaredConstructor(factory.parameterTypes); + } catch (NoSuchMethodException e) { + // a fault in test - fail fast + throw new RuntimeException(e.getMessage()); + } + } + } + } + + /** + * We know fields and methods share the same access control rules. + * We therefore define groups of MemberFactory(s) for members that should + * exhibit same access restrictions in order to allow specifying groups + * instead of individual members in the test cases, making them less verbose. + */ + public enum Group { + // instance field and method pairs + PRIVATE_INSTANCE_F_M(PRIVATE_INSTANCE_FIELD, PRIVATE_INSTANCE_METHOD), + PACKAGE_INSTANCE_F_M(PACKAGE_INSTANCE_FIELD, PACKAGE_INSTANCE_METHOD), + PROTECTED_INSTANCE_F_M(PROTECTED_INSTANCE_FIELD, PROTECTED_INSTANCE_METHOD), + PUBLIC_INSTANCE_F_M(PUBLIC_INSTANCE_FIELD, PUBLIC_INSTANCE_METHOD), + // static field and method pairs + PRIVATE_STATIC_F_M(PRIVATE_STATIC_FIELD, PRIVATE_STATIC_METHOD), + PACKAGE_STATIC_F_M(PACKAGE_STATIC_FIELD, PACKAGE_STATIC_METHOD), + PROTECTED_STATIC_F_M(PROTECTED_STATIC_FIELD, PROTECTED_STATIC_METHOD), + PUBLIC_STATIC_F_M(PUBLIC_STATIC_FIELD, PUBLIC_STATIC_METHOD), + // constructor singles + PRIVATE_C(PRIVATE_CONSTRUCTOR), + PACKAGE_C(PACKAGE_CONSTRUCTOR), + PROTECTED_C(PROTECTED_CONSTRUCTOR), + PUBLIC_C(PUBLIC_CONSTRUCTOR); + + final EnumSet members; + + Group(MemberFactory... members) { + this.members = EnumSet.copyOf(Arrays.asList(members)); + } + + public static EnumSet asSet(Group... groups) { + return groups.length == 0 ? EnumSet.noneOf(Group.class) + : EnumSet.copyOf(Arrays.asList(groups)); + } + } +}