/*
 * Copyright 2020 NAVER Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.navercorp.pinpoint.collector.receiver.thrift.udp;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.DatagramSocket;
import java.net.SocketOption;
import java.net.StandardSocketOptions;

/**
 * @author Taejin Koo
 */
public class ReusePortSocketOptionApplier {

    private static final Logger LOGGER = LogManager.getLogger(ReusePortSocketOptionApplier.class);

    private static final String FIELD_NAME_SO_REUSEPORT = "SO_REUSEPORT";

    private static final SocketOption REUSE_PORT_SOCKET_OPTION = getReusePortSocketOption();

    private final boolean reusePortEnable;
    private final int socketCount;

    private ReusePortSocketOptionApplier(boolean reusePortEnable, int socketCount) {
        this.reusePortEnable = reusePortEnable;
        this.socketCount = socketCount;
    }

    public void apply(DatagramSocket socket) throws IOException {
        if (!reusePortEnable) {
            return;
        }
        try {
            Method setOptionMethod = DatagramSocket.class.getDeclaredMethod("setOption", SocketOption.class, Object.class);
            setOptionMethod.invoke(socket, REUSE_PORT_SOCKET_OPTION, true);
        } catch (Exception e) {
            LOGGER.warn("setOption invoke error", e);
            if (e instanceof IOException) {
                throw (IOException)e;
            }
            throw new IOException("setOption invoke error", e);
        }
    }

    public boolean isReusePortEnable() {
        return reusePortEnable;
    }

    public int getSocketCount() {
        return socketCount;
    }

    public static ReusePortSocketOptionApplier create(boolean reusePort, int socketCount) {
        if (REUSE_PORT_SOCKET_OPTION != null) {
            return new ReusePortSocketOptionApplier(reusePort, socketCount);
        }
        if (reusePort) {
            LOGGER.warn("ReusePort not supported, Please use Jvm9+ for using ReusePort SocketOption");
        }
        return new ReusePortSocketOptionApplier(false, socketCount);
    }

    private static SocketOption getReusePortSocketOption() {
        try {
            Field[] declaredFields = StandardSocketOptions.class.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (declaredField.getName().equals(FIELD_NAME_SO_REUSEPORT)) {
                    Object socketOption = declaredField.get(null);
                    if (socketOption instanceof SocketOption) {
                        LOGGER.info("{} option found", FIELD_NAME_SO_REUSEPORT);
                        return (SocketOption) socketOption;
                    }
                }
            }
        } catch (Exception ignore) {
            // ignores
        }
        return null;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("ReusePortSocketOptionApplier{");
        sb.append("reusePortEnable=").append(reusePortEnable);
        sb.append(", socketCount=").append(socketCount);
        sb.append('}');
        return sb.toString();
    }

}

@@ -652,16 +652,117 @@
 t.udp;%0A%0A
+import com.navercorp.pinpoint.common.util.OsType;%0Aimport com.navercorp.pinpoint.common.util.OsUtils;%0A
 import o
@@ -870,73 +870,8 @@
 on;%0A
-import java.lang.reflect.Field;%0Aimport java.lang.reflect.Method;%0A
 impo
@@ -902,38 +902,8 @@
 et;%0A
-import java.net.SocketOption;%0A
 impo
@@ -1142,149 +1142,77 @@
 nal 
-String FIELD_NAME_SO_REUSEPORT = %22SO_REUSEPORT%22;%0A%0A    private static final SocketOption REUSE_PORT_SOCKET_OPTION = getReusePortSocketOption()
+OsType%5B%5D UNSUPPORTED_OS = new OsType%5B%5D%7BOsType.WINDOW, OsType.SOLARIS%7D
 ;%0A%0A 
@@ -1623,23 +1623,23 @@
         
-Method 
+socket.
 setOptio
@@ -1643,171 +1643,43 @@
 tion
-Method = DatagramSocket.class.getDeclaredMethod(%22setOption%22, SocketOption.class, Object.class);%0A            setOptionMethod.invoke(socket, 
+(StandardSocketOptions.SO_
 REUSE
-_
 PORT
-_SOCKET_OPTION
 , tr
@@ -1692,32 +1692,34 @@
        %7D catch (
+IO
 Exception e) %7B%0A 
@@ -1775,168 +1775,8 @@
 e);%0A
-            if (e instanceof IOException) %7B%0A                throw (IOException)e;%0A            %7D%0A            throw new IOException(%22setOption invoke error%22, e);%0A
     
@@ -2045,131 +2045,33 @@
 if (
-REUSE_PORT_SOCKET_OPTION != null) %7B%0A            return new ReusePortSocketOptionApplier(reusePort, socketCount);%0A   
+isUnsupportedOS()) %7B%0A
     
- %7D%0A
     
@@ -2087,24 +2087,28 @@
 eusePort) %7B%0A
+    
             
@@ -2149,58 +2149,33 @@
 ed, 
-Please use Jvm9+ for using ReusePort SocketOption%22
+OS:%7B%7D%22, OsUtils.getType()
 );%0A 
@@ -2173,34 +2173,42 @@
 ype());%0A        
+ 
-%7D%0A
+   %7D%0A    
         return n
@@ -2256,24 +2256,28 @@
 cketCount);%0A
+    
     %7D%0A%0A    p
@@ -2279,663 +2279,308 @@
     
-private static SocketOption getReusePortSocketOption() %7B%0A        try %7B%0A            Field%5B%5D declaredFields = StandardSocketOptions.class.getDeclaredFields();%0A   
+    return new ReusePortSocketOptionApplier(reusePort, socketCount);%0A
     
+%7D%0A%0A
     
- for (Field declaredField : declaredFields) %7B%0A                if (declaredField.getName().equals(FIELD_NAME_SO_REUSEPORT)) %7B%0A                    Object socketOption = declaredField.get(null);%0A                    if (socketOption instanceof SocketOption) %7B%0A                        LOGGER.info(%22%7B%7D option found%22, FIELD_NAME_SO_REUSEPORT);%0A                        return (SocketOption) socketOption;%0A                    %7D%0A                %7D%0A            %7D%0A        %7D catch (Exception ignore) %7B
+private static boolean isUnsupportedOS() %7B%0A        final OsType osType = OsUtils.getType();%0A        for (OsType unsupportedO : UNSUPPORTED_OS) %7B%0A            if (osType.equals(unsupportedO)) %7B%0A                return true;
 %0A   
@@ -2592,18 +2592,9 @@
     
-// ignores
+%7D
 %0A   
@@ -2619,12 +2619,13 @@
 urn 
-null
+false
 ;%0A  

Remove unnecessary reflection to support reuseport