Repository: apache/dubbo (Java) — Bug Fix

Issue:
[Dubbo - telnet] Unrecognized overload method
- [x] I have searched the [issues](https://github.com/apache/incubator-dubbo/issues) of this repository and believe that this is not a duplicate.
- [x] I have checked the [FAQ](https://github.com/apache/incubator-dubbo/blob/master/FAQ.md) of this repository and believe that this is not a duplicate.

### Environment

* Dubbo version: 2.7.0+
* Operating System version: mac
* Java version: 1.8

### Steps to reproduce this issue

Assume that there is an interface below：

```
package com.youzan.nova.service;

import java.util.Set;

public interface OverrideService {

    void invoke(Integer id, Set<String> orderNumbers);

    void invoke(Long id, Set<String> orderNumbers);

}

package com.youzan.nova.service;

import java.util.Set;

public class OverrideServiceImpl implements OverrideService {
    @Override
    public void invoke(Integer id, Set<String> orderNumbers) {
        System.out.println(id);
    }

    @Override
    public void invoke(Long id, Set<String> orderNumbers) {
        System.out.println(id);
    }
}

```

Start telnet call service：

```
invoke com.youzan.nova.service.OverrideService.invoke("13", Set["1"])
invoke com.youzan.nova.service.OverrideService.invoke("13L", Set["1"])
```

Always only call the method `invoke(Long id, Set<String> orderNumbers)`.


Discussion:

Resolving patch:
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/HelpTelnetHandler.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/HelpTelnetHandler.java
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/HelpTelnetHandler.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/HelpTelnetHandler.java
@@ -59,9 +59,9 @@ public String telnet(Channel channel, String message) {
                     Help help = handler.getClass().getAnnotation(Help.class);
                     List<String> row = new ArrayList<String>();
                     String parameter = " " + extensionLoader.getExtensionName(handler) + " " + (help != null ? help.parameter().replace("\r\n", " ").replace("\n", " ") : "");
-                    row.add(parameter.length() > 50 ? parameter.substring(0, 50) + "..." : parameter);
+                    row.add(parameter.length() > 55 ? parameter.substring(0, 55) + "..." : parameter);
                     String summary = help != null ? help.summary().replace("\r\n", " ").replace("\n", " ") : "";
-                    row.add(summary.length() > 50 ? summary.substring(0, 50) + "..." : summary);
+                    row.add(summary.length() > 55 ? summary.substring(0, 55) + "..." : summary);
                     table.add(row);
                 }
             }
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/telnet/InvokeTelnetHandler.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/telnet/InvokeTelnetHandler.java
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/telnet/InvokeTelnetHandler.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/telnet/InvokeTelnetHandler.java
@@ -30,8 +30,10 @@
 import org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol;
 
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 
 import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -40,21 +42,21 @@
  * InvokeTelnetHandler
  */
 @Activate
-@Help(parameter = "[service.]method(args)", summary = "Invoke the service method.", detail = "Invoke the service method.")
+@Help(parameter = "[service.]method(args) [-p parameter classes]", summary = "Invoke the service method.", detail = "Invoke the service method.")
 public class InvokeTelnetHandler implements TelnetHandler {
 
-    private static Method findMethod(Exporter<?> exporter, String method, List<Object> args) {
+    private static Method findMethod(Exporter<?> exporter, String method, List<Object> args, Class<?>[] paramClases) {
         Invoker<?> invoker = exporter.getInvoker();
         Method[] methods = invoker.getInterface().getMethods();
         for (Method m : methods) {
-            if (m.getName().equals(method) && isMatch(m.getParameterTypes(), args)) {
+            if (m.getName().equals(method) && isMatch(m.getParameterTypes(), args, paramClases)) {
                 return m;
             }
         }
         return null;
     }
 
-    private static boolean isMatch(Class<?>[] types, List<Object> args) {
+    private static boolean isMatch(Class<?>[] types, List<Object> args, Class<?>[] paramClasses) {
         if (types.length != args.size()) {
             return false;
         }
@@ -62,6 +64,10 @@ private static boolean isMatch(Class<?>[] types, List<Object> args) {
             Class<?> type = types[i];
             Object arg = args.get(i);
 
+            if (paramClasses != null && type != paramClasses[i]) {
+                return false;
+            }
+
             if (arg == null) {
                 // if the type is primitive, the method to invoke will cause NullPointerException definitely
                 // so we can offer a specified error message to the invoker in advance and avoid unnecessary invoking
@@ -83,8 +89,8 @@ private static boolean isMatch(Class<?>[] types, List<Object> args) {
                 if (!ReflectUtils.isPrimitive(type)) {
                     return false;
                 }
-                Class<?> boxedType = ReflectUtils.getBoxedClass(type);
-                if (boxedType != arg.getClass()) {
+
+                if (!ReflectUtils.isCompatible(type, arg)) {
                     return false;
                 }
             } else if (arg instanceof Map) {
@@ -121,6 +127,26 @@ public String telnet(Channel channel, String message) {
             buf.append("Use default service " + service + ".\r\n");
         }
         int i = message.indexOf("(");
+        String originalMessage = message;
+        Class<?>[] paramClasses = null;
+        if (message.contains("-p")) {
+            message = originalMessage.substring(0, originalMessage.indexOf("-p")).trim();
+            String paramClassesString = originalMessage.substring(originalMessage.indexOf("-p") + 2).trim();
+            if (paramClassesString.length() > 0) {
+                String[] split = paramClassesString.split("\\s+");
+                if (split.length > 0) {
+                    paramClasses = new Class[split.length];
+                    for (int j = 0; j < split.length; j++) {
+                        try {
+                            paramClasses[j] = Class.forName(split[j]);
+                        } catch (ClassNotFoundException e) {
+                            return "Unknown parameter class for name " + split[j];
+                        }
+                    }
+
+                }
+            }
+        }
         if (i < 0 || !message.endsWith(")")) {
             return "Invalid parameters, format: service.method(args)";
         }
@@ -137,11 +163,26 @@ public String telnet(Channel channel, String message) {
         } catch (Throwable t) {
             return "Invalid json argument, cause: " + t.getMessage();
         }
+        if (paramClasses != null) {
+            if (paramClasses.length != list.size()) {
+                return "Parameter's number does not match the number of parameter class";
+            }
+            List<Object> listOfActualClass = new ArrayList<>(list.size());
+            for (int ii = 0; ii < list.size(); ii++) {
+                if (list.get(ii) instanceof JSONObject) {
+                    JSONObject jsonObject = (JSONObject) list.get(ii);
+                    listOfActualClass.add(jsonObject.toJavaObject(paramClasses[ii]));
+                } else {
+                    listOfActualClass.add(list.get(ii));
+                }
+            }
+            list = listOfActualClass;
+        }
         Invoker<?> invoker = null;
         Method invokeMethod = null;
         for (Exporter<?> exporter : DubboProtocol.getDubboProtocol().getExporters()) {
             if (service == null || service.length() == 0) {
-                invokeMethod = findMethod(exporter, method, list);
+                invokeMethod = findMethod(exporter, method, list, paramClasses);
                 if (invokeMethod != null) {
                     invoker = exporter.getInvoker();
                     break;
@@ -150,7 +191,7 @@ public String telnet(Channel channel, String message) {
                 if (service.equals(exporter.getInvoker().getInterface().getSimpleName())
                         || service.equals(exporter.getInvoker().getInterface().getName())
                         || service.equals(exporter.getInvoker().getUrl().getPath())) {
-                    invokeMethod = findMethod(exporter, method, list);
+                    invokeMethod = findMethod(exporter, method, list, paramClasses);
                     invoker = exporter.getInvoker();
                     break;
                 }
@@ -179,5 +220,4 @@ public String telnet(Channel channel, String message) {
         }
         return buf.toString();
     }
-
 }


Test patch:
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/DemoService.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/DemoService.java
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/DemoService.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/DemoService.java
@@ -59,4 +59,8 @@ public interface DemoService {
 
     long add(int a, long b);
 
+    int getPerson(Person person);
+
+    int getPerson(Person person1, Person perso2);
+
 }
\ No newline at end of file
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/DemoServiceImpl.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/DemoServiceImpl.java
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/DemoServiceImpl.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/support/DemoServiceImpl.java
@@ -107,4 +107,14 @@ public long add(int a, long b) {
         return a + b;
     }
 
+    @Override
+    public int getPerson(Person person) {
+        return 1;
+    }
+
+    @Override
+    public int getPerson(Person person1, Person perso2) {
+        return 2;
+    }
+
 }
\ No newline at end of file
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/telnet/InvokerTelnetHandlerTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/telnet/InvokerTelnetHandlerTest.java
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/telnet/InvokerTelnetHandlerTest.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/telnet/InvokerTelnetHandlerTest.java
@@ -125,6 +125,78 @@ public void testInvokeByPassingEnumValue() throws RemotingException {
     }
 
 
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testComplexParamWithoutSpecifyParamType() throws RemotingException {
+        mockInvoker = mock(Invoker.class);
+        given(mockInvoker.getInterface()).willReturn(DemoService.class);
+        given(mockInvoker.getUrl()).willReturn(URL.valueOf("dubbo://127.0.0.1:20886/demo"));
+        given(mockInvoker.invoke(any(Invocation.class))).willReturn(new RpcResult("ok"));
+        mockChannel = mock(Channel.class);
+        given(mockChannel.getAttribute("telnet.service")).willReturn("org.apache.dubbo.rpc.protocol.dubbo.support.DemoService");
+        given(mockChannel.getLocalAddress()).willReturn(NetUtils.toAddress("127.0.0.1:5555"));
+        given(mockChannel.getRemoteAddress()).willReturn(NetUtils.toAddress("127.0.0.1:20886"));
+
+        DubboProtocol.getDubboProtocol().export(mockInvoker);
+
+        // pass json value to parameter of Person type
+
+        String result = invoke.telnet(mockChannel, "DemoService.getPerson({\"name\":\"zhangsan\",\"age\":12})");
+        assertTrue(result.contains("No such method getPerson in service DemoService"));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testComplexParamSpecifyParamType() throws RemotingException {
+        mockInvoker = mock(Invoker.class);
+        given(mockInvoker.getInterface()).willReturn(DemoService.class);
+        given(mockInvoker.getUrl()).willReturn(URL.valueOf("dubbo://127.0.0.1:20886/demo"));
+        given(mockInvoker.invoke(any(Invocation.class))).willReturn(new RpcResult("ok"));
+        mockChannel = mock(Channel.class);
+        given(mockChannel.getAttribute("telnet.service")).willReturn("org.apache.dubbo.rpc.protocol.dubbo.support.DemoService");
+        given(mockChannel.getLocalAddress()).willReturn(NetUtils.toAddress("127.0.0.1:5555"));
+        given(mockChannel.getRemoteAddress()).willReturn(NetUtils.toAddress("127.0.0.1:20886"));
+
+        DubboProtocol.getDubboProtocol().export(mockInvoker);
+
+        // pass json value to parameter of Person type and specify it's type
+        // one parameter with type of Person
+        String result = invoke.telnet(mockChannel, "DemoService.getPerson({\"name\":\"zhangsan\",\"age\":12}) -p org.apache.dubbo.rpc.protocol.dubbo.support.Person");
+        assertTrue(result.contains("Use default service org.apache.dubbo.rpc.protocol.dubbo.support.DemoService.\r\n\"ok\"\r\n"));
+
+        // two parameter with type of Person
+        result = invoke.telnet(mockChannel, "DemoService.getPerson({\"name\":\"zhangsan\",\"age\":12},{\"name\":\"lisi\",\"age\":12}) " +
+                "-p org.apache.dubbo.rpc.protocol.dubbo.support.Person " +
+                "org.apache.dubbo.rpc.protocol.dubbo.support.Person");
+        assertTrue(result.contains("Use default service org.apache.dubbo.rpc.protocol.dubbo.support.DemoService.\r\n\"ok\"\r\n"));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testComplexParamSpecifyWrongParamType() throws RemotingException {
+        mockInvoker = mock(Invoker.class);
+        given(mockInvoker.getInterface()).willReturn(DemoService.class);
+        given(mockInvoker.getUrl()).willReturn(URL.valueOf("dubbo://127.0.0.1:20886/demo"));
+        given(mockInvoker.invoke(any(Invocation.class))).willReturn(new RpcResult("ok"));
+        mockChannel = mock(Channel.class);
+        given(mockChannel.getAttribute("telnet.service")).willReturn("org.apache.dubbo.rpc.protocol.dubbo.support.DemoService");
+        given(mockChannel.getLocalAddress()).willReturn(NetUtils.toAddress("127.0.0.1:5555"));
+        given(mockChannel.getRemoteAddress()).willReturn(NetUtils.toAddress("127.0.0.1:20886"));
+
+        DubboProtocol.getDubboProtocol().export(mockInvoker);
+
+        // pass json value to parameter of Person type
+        // wrong name of parameter class
+        String result = invoke.telnet(mockChannel, "DemoService.getPerson({\"name\":\"zhangsan\",\"age\":12}) -p wrongType");
+        assertEquals("Unknown parameter class for name wrongType", result);
+
+        // wrong number of parameter class
+        result = invoke.telnet(mockChannel, "DemoService.getPerson({\"name\":\"zhangsan\",\"age\":12},{\"name\":\"lisi\",\"age\":12}) " +
+                "-p org.apache.dubbo.rpc.protocol.dubbo.support.Person");
+        assertEquals("Parameter's number does not match the number of parameter class", result);
+    }
+
+
     @SuppressWarnings("unchecked")
     @Test
     public void testInvokeAutoFindMethod() throws RemotingException {