Repository: apache/flink
File: flink-table/flink-table-common/src/main/java/org/apache/flink/table/types/extraction/ExtractionUtils.java (Java)

Code before:
            // we are tolerant here because frameworks such as Avro accept a boxed type even though
            // the field is primitive
            if (!primitiveToWrapper(parameterType).equals(primitiveToWrapper(fieldType))) {
                return null;
            }
            fieldNames.add(field.getName());
        }

        return fieldNames;
    }

    /**
     * Returns the record component names for the given class if the current JDK supports records
     * (has Class.isRecord / getRecordComponents), and the given class is actually a record.
     */
    static @Nullable List<String> extractRecordComponentNames(Class<?> type) {
        try {
            // Check Class.isRecord() reflectively so it compiles on Java 11
            final Method isRecordMethod = Class.class.getMethod("isRecord");
            final boolean isRecord = (Boolean) isRecordMethod.invoke(type);
            if (!isRecord) {
                return null;
            }

            // Check Class.getRecordComponents() reflectively
            final Method getRecordComponentsMethod = Class.class.getMethod("getRecordComponents");
            final Object componentsArray = getRecordComponentsMethod.invoke(type);
            if (componentsArray == null) {
                return null;
            }

            final int length = Array.getLength(componentsArray);
            final List<String> names = new ArrayList<>(length);
            Method getNameMethod = null;
            for (int i = 0; i < length; i++) {
                final Object component = Array.get(componentsArray, i);
                if (component == null) {
                    continue;
                }
                // Lazily resolve RecordComponent.getName() on the first element
                if (getNameMethod == null) {
                    getNameMethod = component.getClass().getMethod("getName");
                }
                final String name = (String) getNameMethod.invoke(component);
                names.add(name);
            }

            return names;
        } catch (Throwable t) {
            // Running on a JDK that doesn't have records (e.g. Java 11)
            return null;

Review comment (suggestion):
the change looks good - I am wondering what you think about caching the classes and records we have reflectively found, to avoid repeating the reflection calls. 

Code after:
            // we are tolerant here because frameworks such as Avro accept a boxed type even though
            // the field is primitive
            if (!primitiveToWrapper(parameterType).equals(primitiveToWrapper(fieldType))) {
                return null;
            }
            fieldNames.add(field.getName());
        }

        return fieldNames;
    }

    // We check Java record methods reflectively
    // so the code compiles on Java 11
    private static @Nullable Method isRecordMethod;
    private static @Nullable Method getRecordComponentsMethod;
    private static @Nullable Method getRecordComponentNameMethod;

    static {
        try {
            isRecordMethod = Class.class.getMethod("isRecord");
            getRecordComponentsMethod = Class.class.getMethod("getRecordComponents");
            getRecordComponentNameMethod =
                    Class.forName("java.lang.reflect.RecordComponent").getMethod("getName");
        } catch (Throwable t) {
            // For older Java version or to catch any kind of reflective issues
            isRecordMethod = null;
            getRecordComponentsMethod = null;
            getRecordComponentNameMethod = null;
        }
    }

    /**
     * Returns the record component names for the given class if the current JDK supports records
     * and the given class is actually a record.
     */
    static @Nullable List<String> extractRecordComponentNames(Class<?> type) {
        if (isRecordMethod == null
                || getRecordComponentsMethod == null
                || getRecordComponentNameMethod == null) {
            return null;
        }
        try {
            final boolean isRecord = (Boolean) isRecordMethod.invoke(type);
            if (!isRecord) {
                return null;
            }
            final Object componentsArray = getRecordComponentsMethod.invoke(type);
            if (componentsArray == null) {
                return null;
            }