Merge pull request #57 from fangyincheng/master

Fix:java exception
diff --git a/const.go b/const.go
index a8ad45b..53a056f 100644
--- a/const.go
+++ b/const.go
@@ -20,6 +20,7 @@
 
 import (
 	perrors "github.com/pkg/errors"
+	"reflect"
 )
 
 const (
@@ -232,3 +233,5 @@
 
 // DescRegex ...
 var DescRegex, _ = regexp.Compile(DESC_REGEX)
+
+var NilValue = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem())
diff --git a/encode_test.go b/encode_test.go
index fd7c49b..462851f 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"bytes"
+	"os/exec"
 	"testing"
 )
 
@@ -24,3 +25,49 @@
 		t.Fatalf("want %v , got %v", want, got)
 	}
 }
+
+func encodeTarget(target interface{}) ([]byte, error) {
+	e := NewEncoder()
+	err := e.Encode(target)
+	if err != nil {
+		return nil, err
+	}
+	return e.Buffer(), nil
+}
+
+func javaDecodeValidate(method string, target interface{}) (string, error) {
+	b, e := encodeTarget(target)
+	if e != nil {
+		return "", e
+	}
+
+	genHessianJar()
+	cmd := exec.Command("java", "-jar", hessianJar, method)
+
+	stdin, _ := cmd.StdinPipe()
+	_, e = stdin.Write(b)
+	if e != nil {
+		return "", e
+	}
+	e = stdin.Close()
+	if e != nil {
+		return "", e
+	}
+
+	out, e := cmd.Output()
+	if e != nil {
+		return "", e
+	}
+	return string(out), nil
+}
+
+func testJavaDecode(t *testing.T, method string, target interface{}) {
+	r, e := javaDecodeValidate(method, target)
+	if e != nil {
+		t.Errorf("%s: encode fail with error %v", method, e)
+	}
+
+	if r != "true" {
+		t.Errorf("%s: encode %v to bytes wrongly", method, target)
+	}
+}
diff --git a/list.go b/list.go
index 3525981..c55ef5e 100644
--- a/list.go
+++ b/list.go
@@ -186,16 +186,15 @@
 			return nil, perrors.WithStack(err)
 		}
 
-		if it == nil {
-			break
-		}
-
-		v := EnsureRawValue(it)
 		if isVariableArr {
-			aryValue = reflect.Append(aryValue, v)
+			if it != nil {
+				aryValue = reflect.Append(aryValue, EnsureRawValue(it))
+			} else {
+				aryValue = reflect.Append(aryValue, NilValue)
+			}
 			holder.change(aryValue)
 		} else {
-			SetValue(aryValue.Index(j), v)
+			arr[j] = it
 		}
 	}
 
@@ -239,7 +238,11 @@
 		}
 
 		if isVariableArr {
-			aryValue = reflect.Append(aryValue, EnsureRawValue(it))
+			if it != nil {
+				aryValue = reflect.Append(aryValue, EnsureRawValue(it))
+			} else {
+				aryValue = reflect.Append(aryValue, NilValue)
+			}
 			holder.change(aryValue)
 		} else {
 			ary[j] = it
diff --git a/list_test.go b/list_test.go
index 12558c5..1832c8e 100644
--- a/list_test.go
+++ b/list_test.go
@@ -43,6 +43,8 @@
 }
 
 func TestList(t *testing.T) {
+	RegisterPOJOs(new(A0), new(A1))
+
 	testDecodeFramework(t, "replyTypedFixedList_0", []interface{}{})
 	testDecodeFramework(t, "replyTypedFixedList_1", []interface{}{"1"})
 	testDecodeFramework(t, "replyTypedFixedList_7", []interface{}{"1", "2", "3", "4", "5", "6", "7"})
@@ -51,4 +53,18 @@
 	testDecodeFramework(t, "replyUntypedFixedList_1", []interface{}{"1"})
 	testDecodeFramework(t, "replyUntypedFixedList_7", []interface{}{"1", "2", "3", "4", "5", "6", "7"})
 	testDecodeFramework(t, "replyUntypedFixedList_8", []interface{}{"1", "2", "3", "4", "5", "6", "7", "8"})
+
+	testDecodeFramework(t, "customReplyTypedFixedListHasNull", []interface{}{new(A0), new(A1), nil})
+	testDecodeFramework(t, "customReplyTypedVariableListHasNull", []interface{}{new(A0), new(A1), nil})
+	testDecodeFramework(t, "customReplyUntypedFixedListHasNull", []interface{}{new(A0), new(A1), nil})
+	testDecodeFramework(t, "customReplyUntypedVariableListHasNull", []interface{}{new(A0), new(A1), nil})
+}
+
+func TestListEncode(t *testing.T) {
+	testJavaDecode(t, "argUntypedFixedList_0", []interface{}{})
+	testJavaDecode(t, "argUntypedFixedList_1", []interface{}{"1"})
+	testJavaDecode(t, "argUntypedFixedList_7", []interface{}{"1", "2", "3", "4", "5", "6", "7"})
+	testJavaDecode(t, "argUntypedFixedList_8", []interface{}{"1", "2", "3", "4", "5", "6", "7", "8"})
+
+	testJavaDecode(t, "customArgUntypedFixedListHasNull", []interface{}{new(A0), new(A1), nil})
 }
diff --git a/ref.go b/ref.go
index 299556a..e9c08d9 100644
--- a/ref.go
+++ b/ref.go
@@ -28,6 +28,10 @@
 	// record the kind of target, objects are the same only if the address and kind are the same
 	kind reflect.Kind
 
+	// Different struct may share the same address and kind,
+	// so using type information to distinguish them.
+	tp reflect.Type
+
 	// ref index
 	index int
 }
@@ -87,6 +91,7 @@
 func (e *Encoder) checkRefMap(v reflect.Value) (int, bool) {
 	var (
 		kind reflect.Kind
+		tp   reflect.Type
 		addr unsafe.Pointer
 	)
 
@@ -95,6 +100,9 @@
 			v = v.Elem()
 		}
 		kind = v.Elem().Kind()
+		if kind != reflect.Invalid {
+			tp = v.Elem().Type()
+		}
 		if kind == reflect.Slice || kind == reflect.Map {
 			addr = unsafe.Pointer(v.Elem().Pointer())
 		} else {
@@ -102,6 +110,7 @@
 		}
 	} else {
 		kind = v.Kind()
+		tp = v.Type()
 		switch kind {
 		case reflect.Slice, reflect.Map:
 			addr = unsafe.Pointer(v.Pointer())
@@ -111,15 +120,21 @@
 	}
 
 	if elem, ok := e.refMap[addr]; ok {
-		// the array addr is equal to the first elem, which must ignore
 		if elem.kind == kind {
-			return elem.index, ok
+			// If kind is not struct, just return the index. Otherwise,
+			// check whether the types are same, because the different
+			// empty struct may share the same address and kind.
+			if elem.kind != reflect.Struct {
+				return elem.index, ok
+			} else if elem.tp == tp {
+				return elem.index, ok
+			}
 		}
 		return 0, false
 	}
 
 	n := len(e.refMap)
-	e.refMap[addr] = _refElem{kind, n}
+	e.refMap[addr] = _refElem{kind, tp, n}
 	return 0, false
 }
 
diff --git a/test_dubbo/pom.xml b/test_dubbo/pom.xml
index df315c6..92051cf 100644
--- a/test_dubbo/pom.xml
+++ b/test_dubbo/pom.xml
@@ -14,7 +14,7 @@
         <dependency>
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo</artifactId>
-            <version>2.7.0</version>
+            <version>2.7.1</version>
             <scope>compile</scope>
         </dependency>
     </dependencies>
diff --git a/test_hessian/pom.xml b/test_hessian/pom.xml
index f05ecf9..3e2fa8b 100644
--- a/test_hessian/pom.xml
+++ b/test_hessian/pom.xml
@@ -1,6 +1,6 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
- 
+
     <groupId>test</groupId>
     <artifactId>test_hessian</artifactId>
     <version>1.0.0</version>
@@ -9,9 +9,15 @@
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
     </properties>
- 
+
     <dependencies>
         <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>hessian-lite</artifactId>
+            <version>3.2.6</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
             <groupId>com.caucho</groupId>
             <artifactId>hessian</artifactId>
             <version>4.0.60</version>
diff --git a/test_hessian/src/main/java/test/Hessian.java b/test_hessian/src/main/java/test/Hessian.java
index b5f8e98..4865d3a 100644
--- a/test_hessian/src/main/java/test/Hessian.java
+++ b/test_hessian/src/main/java/test/Hessian.java
@@ -14,7 +14,8 @@
 
 package test;
 
-import com.caucho.hessian.io.Hessian2Output;
+import com.alibaba.com.caucho.hessian.io.Hessian2Input;
+import com.alibaba.com.caucho.hessian.io.Hessian2Output;
 import com.caucho.hessian.test.TestHessian2Servlet;
 
 import java.lang.reflect.Method;
@@ -22,18 +23,37 @@
 
 public class Hessian {
     public static void main(String[] args) throws Exception {
-        Method method = null;
-        if (args[0].startsWith("throw_")) {
-            method = TestThrowable.class.getMethod(args[0]);
-        } else {
-            method = TestHessian2Servlet.class.getMethod(args[0]);
+        if (args[0].startsWith("reply")) {
+            Method method = TestHessian2Servlet.class.getMethod(args[0]);
+            TestHessian2Servlet servlet = new TestHessian2Servlet();
+            Object object = method.invoke(servlet);
+
+            Hessian2Output output = new Hessian2Output(System.out);
+            output.writeObject(object);
+            output.flush();
+        } else if (args[0].startsWith("customReply")) {
+            Method method = TestCustomReply.class.getMethod(args[0]);
+            TestCustomReply testCustomReply = new TestCustomReply(System.out);
+            method.invoke(testCustomReply);
+        } else if (args[0].startsWith("arg")) {
+            Hessian2Input input = new Hessian2Input(System.in);
+            Object o = input.readObject();
+
+            Method method = TestHessian2Servlet.class.getMethod(args[0], Object.class);
+            TestHessian2Servlet servlet = new TestHessian2Servlet();
+            System.out.print(method.invoke(servlet, o));
+        } else if (args[0].startsWith("customArg")) {
+            Method method = TestCustomDecode.class.getMethod(args[0]);
+            TestCustomDecode testCustomDecode = new TestCustomDecode(System.in);
+            System.out.print(method.invoke(testCustomDecode));
+        } else if (args[0].startsWith("throw_")) {
+            Method method = method = TestThrowable.class.getMethod(args[0]);
+            TestHessian2Servlet servlet = new TestHessian2Servlet();
+            Object object = method.invoke(servlet);
+
+            Hessian2Output output = new Hessian2Output(System.out);
+            output.writeObject(object);
+            output.flush();
         }
-
-        TestHessian2Servlet servlet = new TestHessian2Servlet();
-        Object object = method.invoke(servlet);
-
-        Hessian2Output output = new Hessian2Output(System.out);
-        output.writeObject(object);
-        output.flush();
     }
 }
\ No newline at end of file
diff --git a/test_hessian/src/main/java/test/TestCustomDecode.java b/test_hessian/src/main/java/test/TestCustomDecode.java
new file mode 100644
index 0000000..05413f0
--- /dev/null
+++ b/test_hessian/src/main/java/test/TestCustomDecode.java
@@ -0,0 +1,43 @@
+// Copyright 2019 Xinge Gao

+//

+// 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 test;

+

+import com.caucho.hessian.io.Hessian2Input;

+import com.caucho.hessian.test.A0;

+import com.caucho.hessian.test.A1;

+

+import java.io.InputStream;

+import java.util.ArrayList;

+import java.util.List;

+

+

+public class TestCustomDecode {

+

+    private Hessian2Input input;

+

+    TestCustomDecode(InputStream is) {

+        input = new Hessian2Input(is);

+    }

+

+    public Object customArgUntypedFixedListHasNull() throws Exception {

+        List list = new ArrayList();

+        list.add(new A0());

+        list.add(new A1());

+        list.add(null);

+

+        Object o = input.readObject();

+        return list.equals(o);

+    }

+}
\ No newline at end of file
diff --git a/test_hessian/src/main/java/test/TestCustomReply.java b/test_hessian/src/main/java/test/TestCustomReply.java
new file mode 100644
index 0000000..e50a49a
--- /dev/null
+++ b/test_hessian/src/main/java/test/TestCustomReply.java
@@ -0,0 +1,118 @@
+// Copyright 2019 Xinge Gao
+//
+// 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 test;
+
+import com.alibaba.com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.test.A0;
+import com.caucho.hessian.test.A1;
+
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.HashMap;
+
+
+public class TestCustomReply {
+
+    private Hessian2Output output;
+    private HashMap<Class<?>, String> typeMap;
+
+    TestCustomReply(OutputStream os) {
+        output = new Hessian2Output(os);
+
+        typeMap = new HashMap<>();
+        typeMap.put(Void.TYPE, "void");
+        typeMap.put(Boolean.class, "boolean");
+        typeMap.put(Byte.class, "byte");
+        typeMap.put(Short.class, "short");
+        typeMap.put(Integer.class, "int");
+        typeMap.put(Long.class, "long");
+        typeMap.put(Float.class, "float");
+        typeMap.put(Double.class, "double");
+        typeMap.put(Character.class, "char");
+        typeMap.put(String.class, "string");
+        typeMap.put(StringBuilder.class, "string");
+        typeMap.put(Object.class, "object");
+        typeMap.put(Date.class, "date");
+        typeMap.put(Boolean.TYPE, "boolean");
+        typeMap.put(Byte.TYPE, "byte");
+        typeMap.put(Short.TYPE, "short");
+        typeMap.put(Integer.TYPE, "int");
+        typeMap.put(Long.TYPE, "long");
+        typeMap.put(Float.TYPE, "float");
+        typeMap.put(Double.TYPE, "double");
+        typeMap.put(Character.TYPE, "char");
+        typeMap.put(boolean[].class, "[boolean");
+        typeMap.put(byte[].class, "[byte");
+        typeMap.put(short[].class, "[short");
+        typeMap.put(int[].class, "[int");
+        typeMap.put(long[].class, "[long");
+        typeMap.put(float[].class, "[float");
+        typeMap.put(double[].class, "[double");
+        typeMap.put(char[].class, "[char");
+        typeMap.put(String[].class, "[string");
+        typeMap.put(Object[].class, "[object");
+    }
+
+    public void customReplyTypedFixedListHasNull() throws Exception {
+        Object[] o = new Object[]{new A0(), new A1(), null};
+        output.writeObject(o);
+        output.flush();
+    }
+
+    public void customReplyTypedVariableListHasNull() throws Exception {
+        Object[] o = new Object[]{new A0(), new A1(), null};
+        if (output.addRef(o)) {
+            return;
+        }
+        boolean hasEnd = output.writeListBegin(-1, typeMap.get(o.getClass()));
+        for (Object tmp: o) {
+            output.writeObject(tmp);
+        }
+        if (hasEnd) {
+            output.writeListEnd();
+        }
+        output.flush();
+    }
+
+    public void customReplyUntypedFixedListHasNull() throws Exception {
+        Object[] o = new Object[]{new A0(), new A1(), null};
+        if (output.addRef(o)) {
+            return;
+        }
+        boolean hasEnd = output.writeListBegin(o.length, null);
+        for (Object tmp: o) {
+            output.writeObject(tmp);
+        }
+        if (hasEnd) {
+            output.writeListEnd();
+        }
+        output.flush();
+    }
+
+    public void customReplyUntypedVariableListHasNull() throws Exception {
+        Object[] o = new Object[]{new A0(), new A1(), null};
+        if (output.addRef(o)) {
+            return;
+        }
+        boolean hasEnd = output.writeListBegin(-1, null);
+        for (Object tmp: o) {
+            output.writeObject(tmp);
+        }
+        if (hasEnd) {
+            output.writeListEnd();
+        }
+        output.flush();
+    }
+}
\ No newline at end of file