001/**
002 * Copyright (C) 2006-2025 Talend Inc. - www.talend.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.talend.sdk.component.runtime.reflect;
017
018import static lombok.AccessLevel.PRIVATE;
019
020import java.lang.invoke.MethodHandles;
021import java.lang.reflect.Constructor;
022import java.lang.reflect.Field;
023import java.lang.reflect.Method;
024
025import lombok.NoArgsConstructor;
026import lombok.extern.slf4j.Slf4j;
027
028@Slf4j
029@NoArgsConstructor(access = PRIVATE)
030public class Defaults {
031
032    private static final Handler HANDLER;
033
034    static {
035        final int javaVersion = JavaVersion.major();
036        final Boolean isJava8 = javaVersion == 8 ? true : false;
037        if (javaVersion > 8 && javaVersion < 17) {
038            try {
039                /**
040                 * Disable Access Warnings: only available below jdk17 (JEP403)
041                 */
042                Class unsafeClazz = Class.forName("sun.misc.Unsafe");
043                Field field = unsafeClazz.getDeclaredField("theUnsafe");
044                field.setAccessible(true);
045                Object unsafe = field.get(null);
046                Method putObjectVolatile =
047                        unsafeClazz.getDeclaredMethod("putObjectVolatile", Object.class, long.class, Object.class);
048                Method staticFieldOffset = unsafeClazz.getDeclaredMethod("staticFieldOffset", Field.class);
049                Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
050                Field loggerField = loggerClass.getDeclaredField("logger");
051                Long offset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
052                putObjectVolatile.invoke(unsafe, loggerClass, offset, null);
053            } catch (Exception e) {
054                System.err.println("Disabling unsafe warnings failed: " + e.getMessage());
055            }
056        }
057        final Constructor<MethodHandles.Lookup> constructor = findLookupConstructor(isJava8);
058        if (isJava8) { // j8
059            HANDLER = (clazz, method, proxy, args) -> constructor
060                    .newInstance(clazz, MethodHandles.Lookup.PRIVATE)
061                    .unreflectSpecial(method, clazz)
062                    .bindTo(proxy)
063                    .invokeWithArguments(args);
064        } else { // j > 8 - can need some --add-opens, we will add a module-info later to be clean when dropping j8
065            final Method privateLookup = findPrivateLookup();
066            HANDLER = (clazz, method, proxy, args) -> MethodHandles.Lookup.class
067                    .cast(privateLookup.invoke(null, clazz, constructor.newInstance(clazz)))
068                    .unreflectSpecial(method, clazz)
069                    .bindTo(proxy)
070                    .invokeWithArguments(args);
071        }
072    }
073
074    public static boolean isDefaultAndShouldHandle(final Method method) {
075        return method.isDefault();
076    }
077
078    public static Object handleDefault(final Class<?> declaringClass, final Method method, final Object proxy,
079            final Object[] args) throws Throwable {
080        return HANDLER.handle(declaringClass, method, proxy, args);
081    }
082
083    private interface Handler {
084
085        Object handle(Class<?> clazz, Method method, Object proxy, Object[] args) throws Throwable;
086    }
087
088    private static Method findPrivateLookup() {
089        try {
090            return MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
091        } catch (final Exception e) {
092            throw new IllegalStateException(e);
093        }
094    }
095
096    private static Constructor<MethodHandles.Lookup> findLookupConstructor(final Boolean isJava8) {
097        try {
098            Constructor<MethodHandles.Lookup> constructor;
099            if (isJava8) {
100                constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
101            } else {
102                constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
103            }
104            if (!constructor.isAccessible()) {
105                // this needs the `--add-opens java.base/java.lang.invoke=ALL-UNNAMED` jvm flag when java9+.
106                constructor.setAccessible(true);
107            }
108            return constructor;
109        } catch (final Exception e) {
110            throw new IllegalStateException(e);
111        }
112    }
113}