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

Simon edited comment on FREEMARKER-183 at 7/30/23 11:45 PM:
------------------------------------------------------------

It seems to work if I add this to the end of 
ClassIntrospector.addBeanInfoToClassIntrospectionData:
{code:java}
if (isRecordType(clazz)) {
    Method[] accessors = RecordAccessor.instance().getAccessors(clazz);
    for (Method accessor : accessors) {
        if (effClassMemberAccessPolicy.isMethodExposed(accessor)) {
            introspData.put(accessor.getName(), new 
FastPropertyDescriptor(accessor, null));
        }
    }
}{code}
We have this at the end so that it overwrites these having been found as 
methods first.
Because they're now defined as properties, you can no longer invoke them as 
methods.

isRecordType:
{code:java}
private static boolean isRecordType(Class<?> clazz) {
    Class<?> parent = clazz.getSuperclass();
    return (parent != null) && "java.lang.Record".equals(parent.getName());
}{code}
RecordAccessor:
{code:java}
public class RecordAccessor {

    private final Method RECORD_GET_RECORD_COMPONENTS;
    private final Method RECORD_COMPONENT_GET_ACCESSOR;

    private final static RecordAccessor INSTANCE;
    private final static RuntimeException PROBLEM;

    static {
        RuntimeException prob = null;
        RecordAccessor inst = null;
        try {
            inst = new RecordAccessor();
        } catch (RuntimeException e) {
            prob = e;
        }
        INSTANCE = inst;
        PROBLEM = prob;
    }

    private RecordAccessor() throws RuntimeException {
        try {
            RECORD_GET_RECORD_COMPONENTS = 
Class.class.getMethod("getRecordComponents");
            Class<?> c = Class.forName("java.lang.reflect.RecordComponent");
            RECORD_COMPONENT_GET_ACCESSOR = c.getMethod("getAccessor");
        } catch (Exception e) {
            throw new RuntimeException(String.format(
                    "Failed to access Methods needed to support 
`java.lang.Record`: (%s) %s",
                    e.getClass().getName(), e.getMessage()), e);
        }
    }

    public static RecordAccessor instance() {
        if (PROBLEM != null) {
            throw PROBLEM;
        }
        return INSTANCE;
    }

    private Object[] recordComponents(Class<?> recordType) throws 
IllegalArgumentException {
        try {
            return (Object[]) RECORD_GET_RECORD_COMPONENTS.invoke(recordType);
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed to access 
RecordComponents of type "
                    + recordType.getName());
        }
    }

    public Method[] getAccessors(Class<?> recordType) throws 
IllegalArgumentException {
        final Object[] components = recordComponents(recordType);
        if (components == null) {
            // not a record, or no reflective access on native image
            return null;
        }
        final Method[] accessors = new Method[components.length];
        for (int i = 0; i < components.length; i++) {
            try {
                accessors[i] = (Method) 
RECORD_COMPONENT_GET_ACCESSOR.invoke(components[i]);
            } catch (Exception e) {
                throw new IllegalArgumentException(String.format(
                        "Failed to access name of field #%d (of %d) of Record 
type %s",
                        i, components.length, recordType.getName()), e);
            }
        }
        return accessors;
    }

}{code}
P.S. This code is adapted from 
[Jackson|https://github.com/FasterXML/jackson-databind] (see JDK14Util.java and 
ClassUtil.java)


was (Author: JIRAUSER301336):
It seems to work if I add this to the end of 
ClassIntrospector.addBeanInfoToClassIntrospectionData:
{code:java}
if (isRecordType(clazz)) {
    Method[] accessors = RecordAccessor.instance().getAccessors(clazz);
    for (Method accessor : accessors) {
        if (effClassMemberAccessPolicy.isMethodExposed(accessor)) {
            introspData.put(accessor.getName(), new 
FastPropertyDescriptor(accessor, null));
        }
    }
}{code}
We have this at the end so that it overwrites these having been found as a 
methods first.
Because they're now defined as properties, you can no longer invoke them as 
methods.

isRecordType:
{code:java}
private static boolean isRecordType(Class<?> clazz) {
    Class<?> parent = clazz.getSuperclass();
    return (parent != null) && "java.lang.Record".equals(parent.getName());
}{code}
RecordAccessor:
{code:java}
public class RecordAccessor {

    private final Method RECORD_GET_RECORD_COMPONENTS;
    private final Method RECORD_COMPONENT_GET_ACCESSOR;

    private final static RecordAccessor INSTANCE;
    private final static RuntimeException PROBLEM;

    static {
        RuntimeException prob = null;
        RecordAccessor inst = null;
        try {
            inst = new RecordAccessor();
        } catch (RuntimeException e) {
            prob = e;
        }
        INSTANCE = inst;
        PROBLEM = prob;
    }

    private RecordAccessor() throws RuntimeException {
        try {
            RECORD_GET_RECORD_COMPONENTS = 
Class.class.getMethod("getRecordComponents");
            Class<?> c = Class.forName("java.lang.reflect.RecordComponent");
            RECORD_COMPONENT_GET_ACCESSOR = c.getMethod("getAccessor");
        } catch (Exception e) {
            throw new RuntimeException(String.format(
                    "Failed to access Methods needed to support 
`java.lang.Record`: (%s) %s",
                    e.getClass().getName(), e.getMessage()), e);
        }
    }

    public static RecordAccessor instance() {
        if (PROBLEM != null) {
            throw PROBLEM;
        }
        return INSTANCE;
    }

    private Object[] recordComponents(Class<?> recordType) throws 
IllegalArgumentException {
        try {
            return (Object[]) RECORD_GET_RECORD_COMPONENTS.invoke(recordType);
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed to access 
RecordComponents of type "
                    + recordType.getName());
        }
    }

    public Method[] getAccessors(Class<?> recordType) throws 
IllegalArgumentException {
        final Object[] components = recordComponents(recordType);
        if (components == null) {
            // not a record, or no reflective access on native image
            return null;
        }
        final Method[] accessors = new Method[components.length];
        for (int i = 0; i < components.length; i++) {
            try {
                accessors[i] = (Method) 
RECORD_COMPONENT_GET_ACCESSOR.invoke(components[i]);
            } catch (Exception e) {
                throw new IllegalArgumentException(String.format(
                        "Failed to access name of field #%d (of %d) of Record 
type %s",
                        i, components.length, recordType.getName()), e);
            }
        }
        return accessors;
    }

}{code}
P.S. This code is adapted from 
[Jackson|https://github.com/FasterXML/jackson-databind] (see JDK14Util.java and 
ClassUtil.java)

> Add support for Java records
> ----------------------------
>
>                 Key: FREEMARKER-183
>                 URL: https://issues.apache.org/jira/browse/FREEMARKER-183
>             Project: Apache Freemarker
>          Issue Type: Task
>            Reporter: Dániel Dékány
>            Assignee: Dániel Dékány
>            Priority: Major
>
> Currently we don't support records (JEP 395), which was finalized in Java 16. 
> Users can extend {{DefaultObjectWrapper}} for that of course, but it should 
> be supported out of the box.



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

Reply via email to