[ 
https://issues.apache.org/jira/browse/LANG-1685?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18020437#comment-18020437
 ] 

Iwauo Tajima commented on LANG-1685:
------------------------------------

I am facing the exact same issue while migrating legacy systems to JDK 21. I 
believe this issue can be resolved by using the following helper method:

{code:java}
private boolean isAccessible(Class<?> targetClass) {
    if (GET_MODULE == null) {
        return true; // JDK 8 (no module system)
    }
    try {
        Object targetModule = GET_MODULE.invoke(targetClass);
        Object selfModule   = GET_MODULE.invoke(getClass());
        return (Boolean) IS_OPEN.invoke(
                targetModule,
                targetClass.getPackage().getName(),
                selfModule);
    } catch (IllegalAccessException | InvocationTargetException e) {
        return false;
    }
}

private static final Method GET_MODULE = initializeGetModuleMethod();
private static Method initializeGetModuleMethod() {
    try {
        return Class.class.getMethod("getModule");
    } catch (NoSuchMethodException e) {
        return null;
    }
}

private static final Method IS_OPEN = initializeIsOpenMethod();
private static Method initializeIsOpenMethod() {
    if (GET_MODULE == null || IS_OPEN == null) {
        return null;
    }
    try {
        Object module = GET_MODULE.invoke(Class.class);
        Class<?> moduleClass = module.getClass();
        return moduleClass.getMethod("isOpen", String.class, moduleClass);
    } catch (NoSuchMethodException | InvocationTargetException | 
IllegalAccessException e) {
        return null;
    }
}
{code}

* The code compiles on JDK 8 or newer.  
* `isAccessible()` always returns `true` on JDK 8 (no module system).  
* On JDK 9+, it returns the result of  
  `targetClass.getModule().isOpen(targetClass.getPackageName(),
  this.getClass().getModule())`.  

Using this helper, we can let *reflectionToString* fall back to 
*Objects.toString()* when the target module is not open, while
retaining backward compatibility with older JDKs.

{code:java}
protected void appendFieldsIn(final Class<?> clazz) {
    if (clazz.isArray()) {
        reflectionAppendArray(getObject());
        return;
    }
    if (!isAccessible(clazz)) {
        appendToString(Objects.toString(getObject()));
        return;
    }
    // … remaining logic …
}
{code}

I would be happy to submit a pull request if you think this solution would be 
useful.



> [JDK17] ToStringBuilder.reflectionToString fails with 
> InaccessibleObjectException on java.lang classes
> ------------------------------------------------------------------------------------------------------
>
>                 Key: LANG-1685
>                 URL: https://issues.apache.org/jira/browse/LANG-1685
>             Project: Commons Lang
>          Issue Type: Bug
>          Components: lang.builder.*
>    Affects Versions: 3.12.0
>            Reporter: David Connard
>            Priority: Major
>
> JDK17 prevents reflective access to java.lang classes by default.
> The following code fails on JDK17+
> {code:java}
> System.out.println("boom = " + 
> ToStringBuilder.reflectionToString(Set.of(123))); {code}
> I understand that we can "--add-opens" (eg. as you've done for hbase builds 
> in 
> [https://github.com/jojochuang/hbase/commit/b909db7ca7c221308ad5aba1ea58317c77358b94)]
>  ... but, ideally, that should not be a standard requirement to run an 
> application that uses {{ToStringBuilder.reflectionToString()}} on JDK17+
> The following sample code appears to work for our use-case, albeit with some 
> additional spurious output on the object.  It catches the exception and just 
> dumps a raw object toString() instead.  You probably want to improve on this.
> {code:java}
> ReflectionToStringBuilder jdk17SafeToStringBuilder = new 
> ReflectionToStringBuilder(obj) {
>     protected void appendFieldsIn(final Class<?> clazz) {
>         if (clazz.isArray()) {
>             this.reflectionAppendArray(this.getObject());
>             return;
>         }
>         // The elements in the returned array are not sorted and are not in 
> any particular order.
>         final Field[] fields = clazz.getDeclaredFields();
>         Arrays.sort(fields, Comparator.comparing(Field::getName));
>         try {
>             // first, check that we can delve into the fields.  With JDK17+, 
> we cannot do this by default on
>             // various JDK classes
>             AccessibleObject.setAccessible(fields, true);
>         } catch (InaccessibleObjectException ioEx) {
>             // JDK 17 - prevents access to fields.  We'll ignore this, and 
> assume these have a decent toString() and not reflect into them
>             this.appendToString(Objects.toString(obj));
>             return;
>         }
>         for (final Field field : fields) {
>             final String fieldName = field.getName();
>             if (this.accept(field)) {
>                 try {
>                     // Warning: Field.get(Object) creates wrappers objects
>                     // for primitive types.
>                     final Object fieldValue = this.getValue(field);
>                     if (!isExcludeNullValues() || fieldValue != null) {
>                         this.append(fieldName, fieldValue, 
> !field.isAnnotationPresent(ToStringSummary.class));
>                     }
>                 } catch (final IllegalAccessException ex) {
>                     //this can't happen. Would get a Security exception
>                     // instead
>                     //throw a runtime exception in case the impossible
>                     // happens.
>                     throw new InternalError("Unexpected 
> IllegalAccessException: " + ex.getMessage());
>                 }
>             }
>         }
>     }
> };
>  {code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to