1717// You should have received a copy of the GNU General Public License
1818// along with rJava. If not, see <http://www.gnu.org/licenses/>.
1919
20- import java .lang .invoke .MethodHandles ;
21- import java .lang .invoke .MethodHandles .Lookup ;
22- import java .lang .reflect .AccessibleObject ;
23- import java .lang .reflect .Constructor ;
24- import java .lang .reflect .Executable ;
20+ import java .lang .reflect .Method ;
2521import java .lang .reflect .Field ;
22+ import java .lang .reflect .Constructor ;
2623import java .lang .reflect .InvocationTargetException ;
27- import java .lang .reflect .Member ;
28- import java .lang .reflect .Method ;
2924import java .lang .reflect .Modifier ;
30- import java .util .ArrayDeque ;
31- import java .util .Arrays ;
32- import java .util .Deque ;
33- import java .util .HashSet ;
25+ import java .lang .reflect .Member ;
26+
3427import java .util .Vector ;
3528
3629
4235 */
4336public class RJavaTools {
4437
45- /* dual path for Java 8 and 9+ to check actual accessibility */
46- private static final Method CAN_ACCESS ;
47- /* Java 8 accessibility checker */
48- private static final Lookup LOOKUP = MethodHandles .publicLookup ();
49-
50- static {
51- Method canAccess = null ;
52- try {
53- canAccess = Executable .class .getMethod ("canAccess" , Object .class );
54- } catch (NoSuchMethodException e ) {
55- // Java 8-
56- } catch (SecurityException e ) {
57- // Java 8-
58- }
59- CAN_ACCESS = canAccess ;
60- }
61-
6238 /**
6339 * Returns an inner class of the class with the given simple name
6440 *
@@ -352,97 +328,7 @@ public static boolean hasMethod(Object o, String name) {
352328 return classHasMethod (o .getClass (), name , false );
353329 }
354330
355- /**
356- * Tests whether a given executable is accessible without actually calling {@link AccessibleObject#isAccessible()} which is unreliable.
357- *
358- * @param executable the given method/constructor
359- * @param o the target object
360- * @return true if the method can be accessed from RJavaTools
361- */
362- public static boolean canAccess (Executable executable , Object o ){
363- try {
364- if (CAN_ACCESS != null ) {
365- /* Java 9+ path */
366- return (boolean ) (Boolean ) CAN_ACCESS .invoke (executable , o );
367- } else {
368- /* Java 8 path */
369- if (executable instanceof Method )
370- LOOKUP .unreflect ((Method ) executable );
371- else
372- LOOKUP .unreflectConstructor ((Constructor ) executable );
373- return true ;
374- }
375- } catch (Exception e ) {
376- return false ;
377- }
378- }
379331
380- /**
381- * Resolves a bridge method to the override one
382- * @param bridgeMethod the potentially bridge method
383- * @return a method that isn't a bridge method
384- */
385- public static Method resolveBridge (Method bridgeMethod ) {
386- // accounts for bridge methods which may have slightly different parameters
387- Method best = null ;
388- if (bridgeMethod .isBridge ()) {
389- Class <?>[] bridgeParams = bridgeMethod .getParameterTypes ();
390- // look for non-bridge methods in the same class that accepts wider parameters
391- nextMethod :
392- for (Method method : bridgeMethod .getDeclaringClass ().getDeclaredMethods ()) {
393- if (!method .isBridge () && !method .isSynthetic () && method .getName ().equals (bridgeMethod .getName ())
394- && method .getParameterCount () == bridgeMethod .getParameterCount ()) {
395- Class <?>[] testParams = method .getParameterTypes ();
396- // Bridge params should be supertypes of target params (erasure widening).
397- for (int i = 0 ; i < testParams .length ; i ++) {
398- if (!testParams [i ].isAssignableFrom (bridgeParams [i ])) continue nextMethod ;
399- }
400- if (bridgeMethod .getReturnType ().isAssignableFrom (method .getReturnType ()) && (best == null || isMoreSpecific (method , best ))) best = method ;
401- }
402- }
403- }
404-
405- return best == null ? bridgeMethod : best ;
406- }
407-
408- /**
409- * Finds a mathod that accepts the given instance and is accessible from RJavaTools class.
410- * <p>Avoids calling isAccessible/setAccessible that fails on Java 17+
411- *
412- * @param method The most specific, possibly non-accessible, method
413- * @param target the instance upon which the method is being invoked (null for static)
414- * @return
415- */
416- public static Method findAccessible (Method method , Object target ) {
417- if (canAccess (method , target )) return method ;
418-
419- Deque <Class <?>> supers = new ArrayDeque <Class <?>>();
420- HashSet <Class <?>> visited = new HashSet <Class <?>>();
421- supers .add (target .getClass ());
422-
423- while (!supers .isEmpty ()) {
424- Class <?> c = supers .pop ();
425- if (visited .add (c )) {
426- Method superMethod ;
427- try {
428- // first update the method to be non-bridge
429- method = resolveBridge (method );
430- superMethod = c .getMethod (method .getName (), method .getParameterTypes ());
431- /* if an accessible executable is found stop here */
432- if (canAccess (superMethod , target )) return superMethod ;
433- if (!c .isInterface () && !c .isArray ()) supers .add (c .getSuperclass ());
434- if (!c .isArray ()) supers .addAll (Arrays .asList (c .getInterfaces ()));
435- } catch (NoSuchMethodException e1 ) {
436- /* executable not found in current class (and any of its ancestors) */
437- } catch (SecurityException e1 ) {
438- /* executable not found in current class (and any of its ancestors) */
439- }
440- }
441- }
442-
443- /* No accessible executable found, fail later on invoke/newInstance */
444- return method ;
445- }
446332
447333 /**
448334 * Object creator. Find the best constructor based on the parameter classes
@@ -457,12 +343,25 @@ public static Object newInstance( Class o_clazz, Object[] args, Class[] clazzes
457343
458344 Constructor cons = getConstructor ( o_clazz , clazzes , is_null );
459345
346+ /* enforcing accessibility (workaround for bug 128) */
347+ boolean access = cons .isAccessible ();
348+ if (!access ) {
349+ try { /* since JDK-17 this may fail */
350+ cons .setAccessible ( true );
351+ } catch (Throwable e ) {
352+ access = true ; /* nothing we can do, just let it fail below */
353+ }
354+ }
355+
460356 Object o ;
461357 try {
462358 o = cons .newInstance ( args ) ;
463359 } catch ( InvocationTargetException e ){
464360 /* the target exception is much more useful than the reflection wrapper */
465- throw e .getCause () ;
361+ throw e .getTargetException () ;
362+ } finally {
363+ if (!access )
364+ cons .setAccessible ( access );
466365 }
467366 return o ;
468367 }
@@ -478,21 +377,32 @@ static boolean[] arg_is_null(Object[] args){
478377
479378 /**
480379 * Invoke a method of a given class
481- * <p>First the appropriate method is resolved by getMethod.
482- * <p>Then, if the method is not accessible, tries to find an accessible equivalent.
483- * <p>Finally it invokes the method
380+ * <p>First the appropriate method is resolved by getMethod and
381+ * then invokes the method
484382 */
485383 public static Object invokeMethod ( Class o_clazz , Object o , String name , Object [] args , Class [] clazzes ) throws Throwable {
486384
487385 Method m = getMethod ( o_clazz , name , clazzes , arg_is_null (args ) );
488- m = findAccessible (m , o );
386+
387+ /* enforcing accessibility (workaround for bug 128) */
388+ boolean access = m .isAccessible ();
389+ if (!access ) {
390+ try { /* since JDK-17 this may fail */
391+ m .setAccessible ( true );
392+ } catch (Throwable e ) {
393+ access = true ; /* nothing we can do, fail later with proper error ... */
394+ }
395+ }
489396
490397 Object out ;
491398 try {
492399 out = m .invoke ( o , args ) ;
493400 } catch ( InvocationTargetException e ){
494401 /* the target exception is much more useful than the reflection wrapper */
495- throw e .getCause () ;
402+ throw e .getTargetException () ;
403+ } finally {
404+ if (!access )
405+ m .setAccessible ( access );
496406 }
497407 return out ;
498408 }
0 commit comments