Merge pull request #102 from aliiohs/feature/FixDateNilBug

Fix: date nil bug
diff --git a/codec.go b/codec.go
index 6c91ce5..184938c 100644
--- a/codec.go
+++ b/codec.go
@@ -292,6 +292,14 @@
 			return
 		}
 	}
+	//temporary process, only handle the same type of situation
+	if v.IsValid() && UnpackPtrType(dest.Type()) == UnpackPtrType(v.Type()) && dest.Kind() == reflect.Ptr && dest.CanSet() {
+		for dest.Type() != v.Type() {
+			v = PackPtr(v)
+		}
+		dest.Set(v)
+		return
+	}
 
 	// if the kind of dest is Ptr, the original value will be zero value
 	// set value on zero value is not allowed
@@ -307,7 +315,6 @@
 		for v.IsValid() && v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Ptr {
 			v = v.Elem()
 		}
-
 		// zero value not need to set
 		if !v.IsValid() {
 			return
@@ -320,7 +327,6 @@
 	} else {
 		v = UnpackPtrValue(v)
 	}
-
 	// zero value not need to set
 	if !v.IsValid() {
 		return
diff --git a/date.go b/date.go
index 8ae4cc6..50723e0 100644
--- a/date.go
+++ b/date.go
@@ -15,6 +15,7 @@
 package hessian
 
 import (
+	"reflect"
 	"time"
 )
 
@@ -26,12 +27,21 @@
 // Date
 /////////////////////////////////////////
 
+var ZeroDate = time.Time{}
+
 // # time in UTC encoded as 64-bit long milliseconds since epoch
 // ::= x4a b7 b6 b5 b4 b3 b2 b1 b0
 // ::= x4b b3 b2 b1 b0       # minutes since epoch
-func encDateInMs(b []byte, v time.Time) []byte {
+func encDateInMs(b []byte, i interface{}) []byte {
+
+	value := UnpackPtrValue(reflect.ValueOf(i))
+	vi := value.Interface().(time.Time)
+	if vi == ZeroDate {
+		b = append(b, BC_NULL)
+		return nil
+	}
 	b = append(b, BC_DATE)
-	return append(b, PackInt64(v.UnixNano()/1e6)...)
+	return append(b, PackInt64(vi.UnixNano()/1e6)...)
 }
 
 func encDateInMimute(b []byte, v time.Time) []byte {
@@ -64,6 +74,8 @@
 	}
 
 	switch {
+	case tag == BC_NULL:
+		return ZeroDate, nil
 	case tag == BC_DATE: //'d': //date
 		s = buf[:8]
 		l, err = d.next(s)
diff --git a/date_test.go b/date_test.go
index 02cc93b..9044002 100644
--- a/date_test.go
+++ b/date_test.go
@@ -18,6 +18,27 @@
 	"testing"
 	"time"
 )
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+func init() {
+	RegisterPOJO(&DateDemo{})
+}
+
+type DateDemo struct {
+	Name    string
+	Date    time.Time
+	Dates   []**time.Time
+	NilDate *time.Time
+	Date1   *time.Time
+	Date2   **time.Time
+	Date3   ***time.Time
+}
+
+func (DateDemo) JavaClassName() string {
+	return "test.model.DateDemo"
+}
 
 func TestEncDate(t *testing.T) {
 	var (
@@ -71,3 +92,64 @@
 	testJavaDecode(t, "argDate_1", time.Date(1998, 5, 8, 9, 51, 31, 0, time.UTC))
 	testJavaDecode(t, "argDate_2", time.Date(1998, 5, 8, 9, 51, 0, 0, time.UTC))
 }
+
+func TestEncDateNull(t *testing.T) {
+	var (
+		v   string
+		tz  time.Time
+		e   *Encoder
+		d   *Decoder
+		res interface{}
+	)
+	v = "2014-02-09 06:15:23 +0800 CST"
+	tz, _ = time.Parse("2006-01-02 15:04:05 +0800 CST", v)
+	d1 := &tz
+	d2 := &d1
+	d3 := &d2
+
+	date := DateDemo{
+		Name:    "zs",
+		Date:    ZeroDate,
+		Dates:   []**time.Time{d2, d2},
+		NilDate: nil,
+		Date1:   nil,
+		Date2:   d2,
+		Date3:   d3,
+	}
+	e = NewEncoder()
+	e.Encode(date)
+	if len(e.Buffer()) == 0 {
+		t.Fail()
+	}
+	d = NewDecoder(e.Buffer())
+	res, _ = d.Decode()
+	assert.Equal(t, ZeroDate, res.(*DateDemo).Date)
+	assert.Equal(t, 2, len(res.(*DateDemo).Dates))
+	assert.Equal(t, tz.Local().String(), (*res.(*DateDemo).Dates[0]).String())
+	assert.Equal(t, &ZeroDate, res.(*DateDemo).NilDate)
+	assert.Equal(t, ZeroDate, *res.(*DateDemo).Date1)
+	assert.Equal(t, tz.Local().String(), (*res.(*DateDemo).Date2).String())
+	assert.Equal(t, tz.Local().String(), (*(*res.(*DateDemo).Date3)).String())
+
+}
+
+func TestDateNulJavaDecode(t *testing.T) {
+	date := DateDemo{
+		Name: "zs",
+		Date: ZeroDate,
+	}
+	testJavaDecode(t, "customArgTypedFixedList_DateNull", date)
+}
+
+func TestDateNilDecode(t *testing.T) {
+
+	doTestDateNull(t, "customReplyTypedFixedDateNull")
+}
+
+func doTestDateNull(t *testing.T, method string) {
+	testDecodeFrameworkFunc(t, method, func(r interface{}) {
+		t.Logf("%#v", r)
+		assert.Equal(t, ZeroDate, r.(*DateDemo).Date)
+		assert.Equal(t, &ZeroDate, r.(*DateDemo).Date1)
+	})
+}
diff --git a/encode.go b/encode.go
index f5f333b..7ae4d05 100644
--- a/encode.go
+++ b/encode.go
@@ -101,8 +101,12 @@
 		e.buffer = encInt64(e.buffer, int64(val))
 
 	case time.Time:
-		e.buffer = encDateInMs(e.buffer, val)
-		// e.buffer = encDateInMimute(v.(time.Time), e.buffer)
+		if ZeroDate == val {
+			e.buffer = encNull(e.buffer)
+		} else {
+			e.buffer = encDateInMs(e.buffer, &val)
+			// e.buffer = encDateInMimute(v.(time.Time), e.buffer)
+		}
 
 	case float32:
 		e.buffer = encFloat(e.buffer, float64(val))
@@ -123,15 +127,21 @@
 		t := UnpackPtrType(reflect.TypeOf(v))
 		switch t.Kind() {
 		case reflect.Struct:
+			vv := reflect.ValueOf(v)
+			vv = UnpackPtr(vv)
+			if !vv.IsValid() {
+				e.buffer = encNull(e.buffer)
+				return nil
+			}
+			if vv.Type().String() == "time.Time" {
+				e.buffer = encDateInMs(e.buffer, v)
+				return nil
+			}
 			if p, ok := v.(POJO); ok {
 				var clazz string
-				vv := reflect.ValueOf(v)
-				vv = UnpackPtr(vv)
-				if vv.IsValid() {
-					clazz = p.JavaClassName()
-					if c, ok := GetSerializer(clazz); ok {
-						return c.EncObject(e, p)
-					}
+				clazz = p.JavaClassName()
+				if c, ok := GetSerializer(clazz); ok {
+					return c.EncObject(e, p)
 				}
 				return e.encObject(p)
 			}
diff --git a/object.go b/object.go
index d9f3cc0..39b7385 100644
--- a/object.go
+++ b/object.go
@@ -424,12 +424,13 @@
 				err error
 				s   interface{}
 			)
-			if fldRawValue.Type().String() == "time.Time" {
+			typ := UnpackPtrType(fldRawValue.Type())
+			if typ.String() == "time.Time" {
 				s, err = d.decDate(TAG_READ)
 				if err != nil {
 					return nil, perrors.WithStack(err)
 				}
-				fldRawValue.Set(reflect.ValueOf(s))
+				SetValue(fldRawValue, EnsurePackValue(s))
 			} else {
 				s, err = d.decObject(TAG_READ)
 				if err != nil {
diff --git a/test_hessian/src/main/java/test/TestCustomDecode.java b/test_hessian/src/main/java/test/TestCustomDecode.java
index 27ac4ce..5eae85f 100644
--- a/test_hessian/src/main/java/test/TestCustomDecode.java
+++ b/test_hessian/src/main/java/test/TestCustomDecode.java
@@ -26,6 +26,7 @@
 import java.util.Date;
 import java.util.List;
 import java.math.BigDecimal;
+import test.model.DateDemo;
 
 public class TestCustomDecode {
 
@@ -168,4 +169,9 @@
         BigDecimal o = (BigDecimal) input.readObject();
         return o.toString().equals( "100.256");
     }
+
+    public Object customArgTypedFixedList_DateNull() throws Exception {
+        DateDemo o = (DateDemo) input.readObject();
+        return o.getDate() == null && o.getDate1() == null;
+    }
 }
\ 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
index 21ada2a..bbe6403 100644
--- a/test_hessian/src/main/java/test/TestCustomReply.java
+++ b/test_hessian/src/main/java/test/TestCustomReply.java
@@ -23,6 +23,7 @@
 import java.util.Date;
 import java.util.HashMap;
 import java.math.BigDecimal;
+import test.model.DateDemo;
 
 public class TestCustomReply {
 
@@ -346,6 +347,12 @@
         output.flush();
     }
 
+    public void customReplyTypedFixedDateNull() throws Exception {
+        DateDemo demo = new DateDemo("zhangshan",null,null);
+        output.writeObject(demo);
+        output.flush();
+    }
+
 }
 
 class TypedListTest implements Serializable {
diff --git a/test_hessian/src/main/java/test/model/DateDemo.java b/test_hessian/src/main/java/test/model/DateDemo.java
new file mode 100644
index 0000000..69a78fb
--- /dev/null
+++ b/test_hessian/src/main/java/test/model/DateDemo.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class DateDemo implements Serializable {
+    private String name;
+    private Date date;
+    private Date date1;
+
+    public String getName() {
+        return name;
+    }
+
+    public DateDemo() {}
+
+    public DateDemo(String name,Date date,Date date1) {
+        this.name = name;
+        this.date = date;
+        this.date1 = date1;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+    public Date getDate() {
+        return date;
+    }
+
+    public void setDate(Date date) {
+        this.date = date;
+    }
+    public Date getDate1() {
+        return date1;
+    }
+
+    public void setDate1(Date date1) {
+        this.date1 = date1;
+    }
+
+}
\ No newline at end of file