diff --git a/pom.xml b/pom.xml
index 8aca797..cce1f68 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.alipay.sofa
hessian
- 3.4.0
+ 3.5.0
jar
${project.groupId}:${project.artifactId}
diff --git a/src/main/java/com/caucho/hessian/io/AbstractFieldAdaptorDeserializer.java b/src/main/java/com/caucho/hessian/io/AbstractFieldAdaptorDeserializer.java
new file mode 100644
index 0000000..22e4b04
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/AbstractFieldAdaptorDeserializer.java
@@ -0,0 +1,41 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author junyuan
+ * @version AbstractFieldAdaptorDeserializer.java, v 0.1 2023年05月06日 14:21 junyuan Exp $
+ */
+public abstract class AbstractFieldAdaptorDeserializer extends AbstractDeserializer {
+
+ protected Map _fields;
+
+ public AbstractFieldAdaptorDeserializer(Class> cl) {
+ _fields = getFieldMapForSerialize(cl);
+ }
+
+ protected Map getFieldMapForSerialize(Class cl) {
+ Map fields = new HashMap();
+ for (; cl != null; cl = cl.getSuperclass()) {
+ Field[] originFields = cl.getDeclaredFields();
+ for (int i = 0; i < originFields.length; i++) {
+ Field field = originFields[i];
+ if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
+ continue;
+ } else if (fields.containsKey(field.getName())) {
+ continue;
+ }
+ fields.put(field.getName(), field);
+ }
+ }
+ return fields;
+ }
+}
diff --git a/src/main/java/com/caucho/hessian/io/AbstractFieldAdaptorSerializer.java b/src/main/java/com/caucho/hessian/io/AbstractFieldAdaptorSerializer.java
new file mode 100644
index 0000000..0ea63f8
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/AbstractFieldAdaptorSerializer.java
@@ -0,0 +1,109 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author junyuan
+ * @version AbstractFieldAdaptorSerializer.java, v 0.1 2023年04月10日 19:34 junyuan Exp $
+ */
+public abstract class AbstractFieldAdaptorSerializer extends AbstractSerializer {
+
+ protected Field[] _fields;
+
+ public AbstractFieldAdaptorSerializer(Class> clazz) {
+ this._fields = getFieldsForSerialize(clazz);
+ }
+
+ public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
+ if (obj == null) {
+ out.writeNull();
+ return;
+ }
+
+ if (out.addRef(obj)) {
+ return;
+ }
+ Class cl = obj.getClass();
+ int ref = out.writeObjectBegin(cl.getName());
+
+ if (ref < -1) {
+ writeObject10(obj, out);
+ }
+ else {
+ if (ref == -1) {
+ writeDefinition20(out);
+ out.writeObjectBegin(cl.getName());
+ }
+
+ writeInstance(obj, out);
+ }
+ }
+
+ private void writeObject10(Object obj, AbstractHessianOutput out)
+ throws IOException
+ {
+ for (int i = 0; i < _fields.length; i++) {
+ Field field = _fields[i];
+
+ out.writeString(field.getName());
+
+ serializeField(out, obj, field);
+ }
+
+ out.writeMapEnd();
+ }
+
+ private void writeDefinition20(AbstractHessianOutput out)
+ throws IOException
+ {
+ out.writeClassFieldLength(_fields.length);
+
+ for (int i = 0; i < _fields.length; i++) {
+ Field field = _fields[i];
+
+ out.writeString(field.getName());
+ }
+ }
+
+ public void writeInstance(Object obj, AbstractHessianOutput out)
+ throws IOException
+ {
+ for (int i = 0; i < _fields.length; i++) {
+ Field field = _fields[i];
+ serializeField(out, obj, field);
+ }
+ }
+
+ protected abstract void serializeField(AbstractHessianOutput out, Object obj, Field field) throws IOException;
+
+ /**
+ * get all fields
+ * include super class
+ * exclude transient or static
+ * @param cl
+ * @return
+ */
+ protected Field[] getFieldsForSerialize(Class cl) {
+ List fields = new ArrayList();
+ for (; cl != null; cl = cl.getSuperclass()) {
+ Field[] originFields = cl.getDeclaredFields();
+ for (int i = 0; i < originFields.length; i++) {
+ Field field = originFields[i];
+ if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+ fields.add(field);
+ }
+ }
+ return fields.toArray(new Field[0]);
+ }
+}
diff --git a/src/main/java/com/caucho/hessian/io/JavaDeserializer.java b/src/main/java/com/caucho/hessian/io/JavaDeserializer.java
index 179bf9d..5bbf646 100644
--- a/src/main/java/com/caucho/hessian/io/JavaDeserializer.java
+++ b/src/main/java/com/caucho/hessian/io/JavaDeserializer.java
@@ -48,6 +48,7 @@
package com.caucho.hessian.io;
+import com.caucho.hessian.util.ReflectionUtil;
import sun.misc.Unsafe;
import java.io.IOException;
@@ -99,7 +100,7 @@ public JavaDeserializer(Class cl)
_readResolve = getReadResolve(cl);
if (_readResolve != null) {
- _readResolve.setAccessible(true);
+ ReflectionUtil.setAccessible(_readResolve);
}
Constructor[] constructors = cl.getDeclaredConstructors();
@@ -138,7 +139,7 @@ else if (param[j].isPrimitive())
}
if (_constructor != null) {
- _constructor.setAccessible(true);
+ ReflectionUtil.setAccessible(_constructor);
Class[] params = _constructor.getParameterTypes();
_constructorArgs = new Object[params.length];
for (int i = 0; i < params.length; i++) {
@@ -325,11 +326,7 @@ else if (fieldMap.get(field.getName()) != null)
continue;
// XXX: could parameterize the handler to only deal with public
- try {
- field.setAccessible(true);
- } catch (Throwable e) {
- e.printStackTrace();
- }
+ ReflectionUtil.setAccessible(field);
Class type = field.getType();
FieldDeserializer deser;
diff --git a/src/main/java/com/caucho/hessian/io/JavaSerializer.java b/src/main/java/com/caucho/hessian/io/JavaSerializer.java
index 327a859..51035b3 100644
--- a/src/main/java/com/caucho/hessian/io/JavaSerializer.java
+++ b/src/main/java/com/caucho/hessian/io/JavaSerializer.java
@@ -48,6 +48,8 @@
package com.caucho.hessian.io;
+import com.caucho.hessian.util.ReflectionUtil;
+
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -70,8 +72,9 @@ public class JavaSerializer extends AbstractSerializer
public JavaSerializer(Class cl)
{
_writeReplace = getWriteReplace(cl);
- if (_writeReplace != null)
- _writeReplace.setAccessible(true);
+ if (_writeReplace != null) {
+ ReflectionUtil.setAccessible(_writeReplace);
+ }
ArrayList primitiveFields = new ArrayList();
ArrayList compoundFields = new ArrayList();
@@ -86,7 +89,7 @@ public JavaSerializer(Class cl)
continue;
// XXX: could parameterize the handler to only deal with public
- field.setAccessible(true);
+ ReflectionUtil.setAccessible(field);
if (field.getType().isPrimitive() ||
field.getType().getName().startsWith("java.lang.") &&
diff --git a/src/main/java/com/caucho/hessian/io/SerializerFactory.java b/src/main/java/com/caucho/hessian/io/SerializerFactory.java
index da84a62..7536e4d 100644
--- a/src/main/java/com/caucho/hessian/io/SerializerFactory.java
+++ b/src/main/java/com/caucho/hessian/io/SerializerFactory.java
@@ -51,6 +51,10 @@
import com.alipay.hessian.ClassNameResolver;
import com.alipay.hessian.ClassNameResolverBuilder;
import com.caucho.burlap.io.BurlapRemoteObject;
+import com.caucho.hessian.io.atomic.AtomicDeserializer;
+import com.caucho.hessian.io.atomic.AtomicSerializer;
+import com.caucho.hessian.io.java17.base.JavaCurrencyDeserializer;
+import com.caucho.hessian.io.java17.base.JavaCurrencySerializer;
import com.caucho.hessian.io.java8.DurationHandle;
import com.caucho.hessian.io.java8.InstantHandle;
import com.caucho.hessian.io.java8.Java8TimeSerializer;
@@ -66,12 +70,22 @@
import com.caucho.hessian.io.java8.ZoneIdSerializer;
import com.caucho.hessian.io.java8.ZoneOffsetHandle;
import com.caucho.hessian.io.java8.ZonedDateTimeHandle;
+import com.caucho.hessian.io.throwable.StackTraceElementDeserializer;
+import com.caucho.hessian.io.throwable.StackTraceElementSerializer;
+import com.caucho.hessian.io.throwable.ThrowableHelper;
import java.io.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -107,6 +121,7 @@ public class SerializerFactory extends AbstractSerializerFactory
protected ClassNameResolver classNameResolver = ClassNameResolverBuilder.buildDefault();
protected final static boolean isHigherThanJdk8 = isJava8();
+ protected final static boolean isHigherThanJdk17 = isJava17();
private Map> _typeNotFoundMap = new ConcurrentHashMap>(
8);
@@ -236,7 +251,7 @@ else if (cl.isArray())
serializer = new ArraySerializer();
else if (Throwable.class.isAssignableFrom(cl))
- serializer = new ThrowableSerializer(cl);
+ serializer = ThrowableHelper.getSerializer(cl);
else if (InputStream.class.isAssignableFrom(cl))
serializer = new InputStreamSerializer();
@@ -343,6 +358,9 @@ else if (Enumeration.class.isAssignableFrom(cl))
else if (Enum.class.isAssignableFrom(cl))
deserializer = new EnumDeserializer(cl);
+ else if (Throwable.class.isAssignableFrom(cl))
+ deserializer = ThrowableHelper.getDeserializer(cl);
+
else
deserializer = getDefaultDeserializer(cl);
@@ -620,7 +638,7 @@ protected static void addBasic(Class cl, String typeName, int type)
try {
Class stackTrace = Class.forName("java.lang.StackTraceElement");
-
+ _staticSerializerMap.put(stackTrace, new StackTraceElementSerializer());
_staticDeserializerMap.put(stackTrace, new StackTraceElementDeserializer());
} catch (Throwable e) {
}
@@ -663,6 +681,32 @@ protected static void addBasic(Class cl, String typeName, int type)
log.warning(String.valueOf(t.getCause()));
}
+ try {
+ AtomicSerializer atomicSerializer = new AtomicSerializer();
+ _staticSerializerMap.put(AtomicInteger.class, atomicSerializer);
+ _staticSerializerMap.put(AtomicLong.class, atomicSerializer);
+ _staticSerializerMap.put(AtomicBoolean.class, atomicSerializer);
+ _staticSerializerMap.put(AtomicReference.class, atomicSerializer);
+ _staticSerializerMap.put(AtomicLongArray.class, atomicSerializer);
+ _staticSerializerMap.put(AtomicIntegerArray.class, atomicSerializer);
+ _staticSerializerMap.put(AtomicReferenceArray.class, atomicSerializer);
+
+ _staticDeserializerMap.put(AtomicInteger.class, new AtomicDeserializer(AtomicInteger.class));
+ _staticDeserializerMap.put(AtomicLong.class, new AtomicDeserializer(AtomicLong.class));
+ _staticDeserializerMap.put(AtomicBoolean.class, new AtomicDeserializer(AtomicBoolean.class));
+ _staticDeserializerMap.put(AtomicReference.class, new AtomicDeserializer(AtomicReference.class));
+ _staticDeserializerMap.put(AtomicLongArray.class, new AtomicDeserializer(AtomicLongArray.class));
+ _staticDeserializerMap.put(AtomicIntegerArray.class, new AtomicDeserializer(AtomicIntegerArray.class));
+ _staticDeserializerMap.put(AtomicReferenceArray.class, new AtomicDeserializer(AtomicReferenceArray.class));
+
+ } catch (Throwable t) {
+ log.warning(String.valueOf(t.getCause()));
+ }
+
+ if (isHigherThanJdk17) {
+ addCurrencySupport();
+ }
+
}
/**
@@ -675,6 +719,27 @@ private static boolean isJava8() {
return Double.valueOf(javaVersion) >= 1.8;
}
+ /**
+ * check if the environment is java 17 or beyond
+ *
+ * @return if on java 17
+ */
+ private static boolean isJava17() {
+ String javaVersion = System.getProperty("java.specification.version");
+ return Double.valueOf(javaVersion) >= 17;
+ }
+
+ protected static void addCurrencySupport() {
+ try {
+ JavaCurrencySerializer currencySerializer = new JavaCurrencySerializer(Currency.class);
+ JavaCurrencyDeserializer currencyDeserializer = new JavaCurrencyDeserializer();
+ _staticSerializerMap.put(Currency.class, currencySerializer);
+ _staticDeserializerMap.put(Currency.class, currencyDeserializer);
+ } catch (Throwable t) {
+ log.warning(String.valueOf(t.getCause()));
+ }
+ }
+
private static boolean isZoneId(Class cl) {
try {
return isHigherThanJdk8 && Class.forName("java.time.ZoneId").isAssignableFrom(cl);
diff --git a/src/main/java/com/caucho/hessian/io/StackTraceElementDeserializer.java b/src/main/java/com/caucho/hessian/io/StackTraceElementDeserializer.java
deleted file mode 100644
index a60dfe8..0000000
--- a/src/main/java/com/caucho/hessian/io/StackTraceElementDeserializer.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2001-2004 Caucho Technology, Inc. All rights reserved.
- *
- * The Apache Software License, Version 1.1
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * 3. The end-user documentation included with the redistribution, if
- * any, must include the following acknowlegement:
- * "This product includes software developed by the
- * Caucho Technology (http://www.caucho.com/)."
- * Alternately, this acknowlegement may appear in the software itself,
- * if and wherever such third-party acknowlegements normally appear.
- *
- * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
- * endorse or promote products derived from this software without prior
- * written permission. For written permission, please contact
- * info@caucho.com.
- *
- * 5. Products derived from this software may not be called "Resin"
- * nor may "Resin" appear in their names without prior written
- * permission of Caucho Technology.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
- * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
- * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
- * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
- * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * @author Scott Ferguson
- */
-
-package com.caucho.hessian.io;
-
-/**
- * Deserializing a JDK 1.4 StackTraceElement
- * @author pangu
- */
-public class StackTraceElementDeserializer extends JavaDeserializer {
- public StackTraceElementDeserializer() {
- super(StackTraceElement.class);
- }
-
- @Override
- protected Object instantiate() throws Exception {
- return new StackTraceElement("", "", "", 0);
- }
-}
diff --git a/src/main/java/com/caucho/hessian/io/ThrowableSerializer.java b/src/main/java/com/caucho/hessian/io/ThrowableSerializer.java
deleted file mode 100644
index 628dd81..0000000
--- a/src/main/java/com/caucho/hessian/io/ThrowableSerializer.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (c) 2001-2004 Caucho Technology, Inc. All rights reserved.
- *
- * The Apache Software License, Version 1.1
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * 3. The end-user documentation included with the redistribution, if
- * any, must include the following acknowlegement:
- * "This product includes software developed by the
- * Caucho Technology (http://www.caucho.com/)."
- * Alternately, this acknowlegement may appear in the software itself,
- * if and wherever such third-party acknowlegements normally appear.
- *
- * 4. The names "Burlap", "Resin", and "Caucho" must not be used to
- * endorse or promote products derived from this software without prior
- * written permission. For written permission, please contact
- * info@caucho.com.
- *
- * 5. Products derived from this software may not be called "Resin"
- * nor may "Resin" appear in their names without prior written
- * permission of Caucho Technology.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
- * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
- * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
- * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
- * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * @author Scott Ferguson
- */
-
-package com.caucho.hessian.io;
-
-import java.io.IOException;
-
-/**
- * Serializing an object for known object types.
- */
-public class ThrowableSerializer extends JavaSerializer {
- public ThrowableSerializer(Class cl)
- {
- super(cl);
- }
-
- public void writeObject(Object obj, AbstractHessianOutput out)
- throws IOException
- {
- Throwable e = (Throwable) obj;
-
- e.getStackTrace();
-
- super.writeObject(obj, out);
- }
-}
diff --git a/src/main/java/com/caucho/hessian/io/atomic/AtomicDeserializer.java b/src/main/java/com/caucho/hessian/io/atomic/AtomicDeserializer.java
new file mode 100644
index 0000000..d97ce8b
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/atomic/AtomicDeserializer.java
@@ -0,0 +1,101 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.atomic;
+
+import com.caucho.hessian.io.AbstractDeserializer;
+import com.caucho.hessian.io.AbstractHessianInput;
+import com.caucho.hessian.io.BasicDeserializer;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ *
+ * @author junyuan
+ * @version AtomicDeserializer.java, v 0.1 2023年03月31日 17:34 junyuan Exp $
+ */
+public class AtomicDeserializer extends AbstractDeserializer {
+
+ private Class> _type;
+
+ private BasicDeserializer intArrayDsr = new BasicDeserializer(BasicDeserializer.INTEGER_ARRAY);
+ private BasicDeserializer longArrayDsr = new BasicDeserializer(BasicDeserializer.LONG_ARRAY);
+
+ public AtomicDeserializer(Class> cl) {
+ this._type = cl;
+ }
+
+ @Override
+ public Object readObject(AbstractHessianInput in, String[] fieldNames) throws IOException {
+
+ if (AtomicInteger.class.equals(_type)) {
+ AtomicInteger tmp = new AtomicInteger();
+ in.addRef(tmp);
+ tmp.set(in.readInt());
+ return tmp;
+ }
+ else if (AtomicBoolean.class.equals(_type)) {
+ AtomicBoolean tmp = new AtomicBoolean();
+ in.addRef(tmp);
+ tmp.set(in.readInt() == 1);
+ return tmp;
+ }
+ else if (AtomicLong.class.equals(_type)) {
+ AtomicLong tmp = new AtomicLong();
+ in.addRef(tmp);
+ tmp.set(in.readLong());
+ return tmp;
+ }
+ else if (AtomicReference.class.equals(_type)) {
+ AtomicReference tmp = new AtomicReference();
+ in.addRef(tmp);
+ tmp.set(in.readObject());
+ return tmp;
+ }
+ else if (AtomicIntegerArray.class.equals(_type)) {
+ AtomicIntegerArray array = null;
+ int ref = in.addRef(array);
+ int[] res = (int[]) intArrayDsr.readObject(in);
+ int len = res.length;
+ array = new AtomicIntegerArray(len);
+ for (int i = 0; i < len; i++) {
+ array.set(i, res[i]);
+ }
+ in.setRef(ref, array);
+ return array;
+ }
+ else if (AtomicLongArray.class.equals(_type)) {
+ AtomicLongArray array = null;
+ int ref = in.addRef(array);
+ long[] res = (long[]) longArrayDsr.readObject(in);
+ int len = res.length;
+ array = new AtomicLongArray(len);
+ for (int i = 0; i < len; i++) {
+ array.set(i, res[i]);
+ }
+ in.setRef(ref, array);
+ return array;
+ }
+ else if (AtomicReferenceArray.class.equals(_type)) {
+ int ref = in.addRef(null);
+ Object[] res = (Object[]) in.readObject((new Object[0]).getClass());
+ int len = res.length;
+ AtomicReferenceArray array = new AtomicReferenceArray(len);
+ for (int i = 0; i < len; i++) {
+ array.set(i, res[i]);
+ }
+ in.setRef(ref, array);
+ return array;
+ }
+
+ throw new UnsupportedOperationException(String.valueOf(this));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/caucho/hessian/io/atomic/AtomicSerializer.java b/src/main/java/com/caucho/hessian/io/atomic/AtomicSerializer.java
new file mode 100644
index 0000000..5ac4a4e
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/atomic/AtomicSerializer.java
@@ -0,0 +1,124 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.atomic;
+
+import com.caucho.hessian.io.AbstractHessianOutput;
+import com.caucho.hessian.io.AbstractSerializer;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ *
+ * @author junyuan
+ * @version AtomicSerializer.java, v 0.1 2023年03月30日 17:29 junyuan Exp $
+ */
+public class AtomicSerializer extends AbstractSerializer {
+ @Override
+ public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
+ if (obj == null) {
+ out.writeNull();
+ return;
+ }
+
+ Object value;
+ if (obj instanceof AtomicBoolean) {
+ doWrite(obj, "value", out);
+ }
+ else if (obj instanceof AtomicInteger) {
+ doWrite(obj, "value", out);
+ }
+ else if (obj instanceof AtomicLong) {
+ doWrite(obj, "value", out);
+ }
+ else if (obj instanceof AtomicReference) {
+ doWrite(obj, "value", out);
+ }
+ else if (obj instanceof AtomicIntegerArray) {
+ doWrite(obj, "array", out);
+ }
+ else if (obj instanceof AtomicLongArray) {
+ doWrite(obj, "array", out);
+ }
+ else if (obj instanceof AtomicReferenceArray) {
+ doWrite(obj, "array", out);
+ }
+ else {
+ throw new UnsupportedOperationException(String.valueOf(this));
+ }
+ }
+
+ protected void doWrite(Object obj, String fieldName, AbstractHessianOutput out) throws IOException {
+ if (out.addRef(obj)) {
+ return;
+ }
+ Class cl = obj.getClass();
+ int ref = out.writeObjectBegin(cl.getName()); // atomicinteger.class
+ if (ref < -1) {
+ // writeObject10(obj, out);
+ out.writeString(fieldName/*field name*/);
+ // field do serialize
+ writeFieldValue(obj, out);
+ }
+ else {
+ if (ref == -1) {
+ out.writeClassFieldLength(1);
+ out.writeString(fieldName/*field name*/);
+ out.writeObjectBegin(cl.getName());
+ }
+ // field foreach do serialize
+ writeFieldValue(obj, out);
+ }
+ }
+
+ private void writeFieldValue(Object obj, AbstractHessianOutput out) throws IOException {
+ if (obj instanceof AtomicInteger) {
+ out.writeInt(((AtomicInteger) obj).get());
+ }
+ else if (obj instanceof AtomicLong) {
+ out.writeLong(((AtomicLong) obj).get());
+ }
+ else if (obj instanceof AtomicBoolean) {
+ out.writeInt(((AtomicBoolean) obj).get() ? 1 : 0);
+ }
+ else if (obj instanceof AtomicReference) {
+ out.writeObject(((AtomicReference) obj).get());
+ }
+ else if (obj instanceof AtomicIntegerArray) {
+ AtomicIntegerArray array = (AtomicIntegerArray) obj;
+ int len = array.length();
+ int[] tmp = new int[len];
+ for (int i = 0; i < len; i++) {
+ tmp[i] = array.get(i);
+ }
+ out.writeObject(tmp);
+ }
+ else if (obj instanceof AtomicLongArray) {
+ AtomicLongArray array = (AtomicLongArray) obj;
+ int len = array.length();
+ long[] tmp = new long[len];
+ for (int i = 0; i < len; i++) {
+ tmp[i] = array.get(i);
+ }
+ out.writeObject(tmp);
+ }
+ else if (obj instanceof AtomicReferenceArray) {
+ AtomicReferenceArray array = (AtomicReferenceArray) obj;
+ int len = array.length();
+ Object[] tmp = new Object[len];
+ for (int i = 0; i < len; i++) {
+ tmp[i] = array.get(i);
+ }
+ out.writeObject(tmp);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/caucho/hessian/io/java17/base/JavaCurrencyDeserializer.java b/src/main/java/com/caucho/hessian/io/java17/base/JavaCurrencyDeserializer.java
new file mode 100644
index 0000000..9bbb8a1
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/java17/base/JavaCurrencyDeserializer.java
@@ -0,0 +1,33 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.java17.base;
+
+import com.caucho.hessian.io.AbstractDeserializer;
+import com.caucho.hessian.io.AbstractHessianInput;
+
+import java.io.IOException;
+import java.util.Currency;
+
+/**
+ *
+ * @author junyuan
+ * @version JavaCurrencyDeserializer.java, v 0.1 2023年08月09日 10:53 junyuan Exp $
+ */
+public class JavaCurrencyDeserializer extends AbstractDeserializer {
+
+ @Override
+ public Object readObject(AbstractHessianInput in, String[] fieldNames) throws IOException {
+ int ref = in.addRef(null);
+ String currencyCode = in.readString();
+ Currency currency = null;
+ try {
+ // 如果该数据有问题, 保证至少塞入一个 null 作为 ref
+ currency = Currency.getInstance(currencyCode);
+ } finally {
+ in.setRef(ref, currency);
+ }
+ return currency;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/caucho/hessian/io/java17/base/JavaCurrencySerializer.java b/src/main/java/com/caucho/hessian/io/java17/base/JavaCurrencySerializer.java
new file mode 100644
index 0000000..37fded7
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/java17/base/JavaCurrencySerializer.java
@@ -0,0 +1,34 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.java17.base;
+
+import com.caucho.hessian.io.AbstractFieldAdaptorSerializer;
+import com.caucho.hessian.io.AbstractHessianOutput;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Currency;
+
+/**
+ *
+ * @author junyuan
+ * @version JavaCurrencySerializer.java, v 0.1 2023年08月09日 10:32 junyuan Exp $
+ */
+public class JavaCurrencySerializer extends AbstractFieldAdaptorSerializer {
+
+ public JavaCurrencySerializer(Class> clazz) {
+ super(clazz);
+ }
+
+ @Override
+ protected void serializeField(AbstractHessianOutput out, Object obj, Field field)
+ throws IOException {
+ Currency currency = (Currency) obj;
+ if ("currencyCode".equals(field.getName())) {
+ String currencyCode = currency.getCurrencyCode();
+ out.writeString(currencyCode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/caucho/hessian/io/throwable/ReflectThrowableSerializer.java b/src/main/java/com/caucho/hessian/io/throwable/ReflectThrowableSerializer.java
new file mode 100644
index 0000000..048ffbb
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/throwable/ReflectThrowableSerializer.java
@@ -0,0 +1,28 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.AbstractHessianOutput;
+import com.caucho.hessian.io.JavaSerializer;
+
+import java.io.IOException;
+
+/**
+ * use under jdk 17
+ * @author junyuan
+ * @version ReflectThrowableSerializer.java, v 0.1 2023年05月06日 10:46 junyuan Exp $
+ */
+public class ReflectThrowableSerializer extends JavaSerializer {
+ public ReflectThrowableSerializer(Class cl) {
+ super(cl);
+ }
+
+ @Override
+ public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
+ // 如果需要反射操作获取 stack trace, 这里需要先 get 一下
+ ((Throwable) obj).getStackTrace();
+ super.writeObject(obj, out);
+ }
+}
diff --git a/src/main/java/com/caucho/hessian/io/throwable/StackTraceElementDeserializer.java b/src/main/java/com/caucho/hessian/io/throwable/StackTraceElementDeserializer.java
new file mode 100644
index 0000000..f2dcdf5
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/throwable/StackTraceElementDeserializer.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2001-2004 Caucho Technology, Inc. All rights reserved.
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Caucho Technology (http://www.caucho.com/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
+ * endorse or promote products derived from this software without prior
+ * written permission. For written permission, please contact
+ * info@caucho.com.
+ *
+ * 5. Products derived from this software may not be called "Resin"
+ * nor may "Resin" appear in their names without prior written
+ * permission of Caucho Technology.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @author Scott Ferguson
+ */
+
+package com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.AbstractFieldAdaptorDeserializer;
+import com.caucho.hessian.io.AbstractHessianInput;
+import com.caucho.hessian.io.IOExceptionWrapper;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Deserializing a JDK 1.4 StackTraceElement
+ * @author pangu
+ */
+public class StackTraceElementDeserializer extends AbstractFieldAdaptorDeserializer {
+ protected static final Logger log = Logger.getLogger(StackTraceElementSerializer.class
+ .getName());
+
+ private Constructor _defaultConstructor = null;
+
+ private Constructor _constructorJdk9 = null;
+
+ @Override
+ public Class getType() {
+ return StackTraceElement.class;
+ }
+
+ public StackTraceElementDeserializer() {
+ super(StackTraceElement.class);
+
+ try {
+ if (_fields.size() > 4) {
+ // available since java 9
+ _constructorJdk9 = StackTraceElement.class.getDeclaredConstructor(String.class, String.class,
+ String.class, String.class,
+ String.class, String.class, int.class);
+ }
+ // default, only read class, method, file and line
+ _defaultConstructor = StackTraceElement.class.getDeclaredConstructor(String.class, String.class,
+ String.class, int.class);
+ } catch (Exception e) {
+ log.log(Level.FINE, e.toString(), e);
+ }
+
+ }
+
+ @Override
+ public Object readObject(AbstractHessianInput in, String[] fieldNames) throws IOException {
+ try {
+ Object tmp;
+ if (_constructorJdk9 != null) {
+ tmp = _constructorJdk9.newInstance("", "", "", "", "", "", 0);
+ } else {
+ tmp = _defaultConstructor.newInstance("", "", "", 0);
+ }
+
+ int ref = in.addRef(tmp);
+ Map fieldValueMap = new HashMap();
+
+ for (int i = 0; i < fieldNames.length; i++) {
+ String name = fieldNames[i];
+ Field field = _fields.get(name);
+
+ if (String.class.equals(field.getType())) {
+ fieldValueMap.put(name, in.readString());
+ } else if (int.class.equals(field.getType())) {
+ fieldValueMap.put(name, in.readInt());
+ }
+ }
+
+ StackTraceElement obj;
+ if (_constructorJdk9 != null) {
+ obj = _constructorJdk9.newInstance(
+ fieldValueMap.get("classLoaderName"), fieldValueMap.get("moduleName"),
+ fieldValueMap.get("moduleVersion"), fieldValueMap.get("declaringClass"),
+ fieldValueMap.get("methodName"), fieldValueMap.get("fileName"),
+ fieldValueMap.get("lineNumber"));
+ } else if (_defaultConstructor != null) {
+ obj = _defaultConstructor.newInstance(
+ fieldValueMap.get("declaringClass"), fieldValueMap.get("methodName"),
+ fieldValueMap.get("fileName"), fieldValueMap.get("lineNumber"));
+ } else {
+ throw new UnsupportedOperationException("no constructor for " + getType().getName() + " found");
+ }
+
+ in.setRef(ref, obj);
+
+ return obj;
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IOExceptionWrapper(StackTraceElement.class.getName() + ":" + e, e);
+ }
+ }
+
+ @Override
+ protected Map getFieldMapForSerialize(Class cl) {
+ Map fields = new HashMap();
+ for (; cl != null; cl = cl.getSuperclass()) {
+ Field[] originFields = cl.getDeclaredFields();
+ for (int i = 0; i < originFields.length; i++) {
+ Field field = originFields[i];
+ if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
+ continue;
+ } else if (fields.containsKey(field.getName()) || "format".equals(field.getName())) {
+ continue;
+ }
+ fields.put(field.getName(), field);
+ }
+ }
+ return fields;
+ }
+}
diff --git a/src/main/java/com/caucho/hessian/io/throwable/StackTraceElementSerializer.java b/src/main/java/com/caucho/hessian/io/throwable/StackTraceElementSerializer.java
new file mode 100644
index 0000000..cdf429d
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/throwable/StackTraceElementSerializer.java
@@ -0,0 +1,110 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.AbstractFieldAdaptorSerializer;
+import com.caucho.hessian.io.AbstractHessianOutput;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author junyuan
+ * @version StackTraceElementSerializer.java, v 0.1 2023年04月10日 11:12 junyuan Exp $
+ */
+public class StackTraceElementSerializer extends AbstractFieldAdaptorSerializer {
+ protected static final Logger log = Logger
+ .getLogger(StackTraceElementSerializer.class
+ .getName());
+
+ private final Class _clazz = StackTraceElement.class;
+
+ private final static String GET_PREFIX = "get";
+
+ private Map _readMethods = new HashMap();
+
+ public StackTraceElementSerializer() {
+ super(StackTraceElement.class);
+
+ // get getter
+ for (Field field : _fields) {
+ String fieldName = field.getName();
+
+ String methodName = GET_PREFIX + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
+ if ("declaringClass".equals(fieldName)) {
+ methodName = GET_PREFIX + "ClassName";
+ }
+
+ try {
+ Method m = _clazz.getMethod(methodName);
+ _readMethods.put(fieldName, m);
+ } catch (NoSuchMethodException e) {
+ log.log(Level.WARNING, "getter not found: " + methodName, e);
+ } catch (Exception e) {
+ log.log(Level.WARNING, e.toString(), e);
+ }
+ }
+ }
+
+ @Override
+ protected void serializeField(AbstractHessianOutput out, Object obj, Field field)
+ throws IOException {
+ if (!_readMethods.containsKey(field.getName())) {
+ out.writeNull();
+ return;
+ }
+
+ // only String and int field is required to be serialized
+ if (String.class.equals(field.getType())) {
+ String value = null;
+ try {
+ value = (String) _readMethods.get(field.getName()).invoke(obj);
+ } catch (Exception e) {
+ log.log(Level.FINE, e.toString(), e);
+ }
+ out.writeString(value);
+ } else if (int.class.equals(field.getType())) {
+ Integer value = 0;
+ try {
+ value = (Integer) _readMethods.get(field.getName()).invoke(obj);
+ } catch (Exception e) {
+ log.log(Level.FINE, e.toString(), e);
+ }
+ out.writeInt(value);
+ } else {
+ log.warning("unsupported field " + field.getName() + "(" + field.getType() + "), will write null");
+ out.writeNull();
+ }
+ }
+
+ @Override
+ protected Field[] getFieldsForSerialize(Class cl) {
+ List fields = new ArrayList();
+ for (; cl != null; cl = cl.getSuperclass()) {
+ Field[] originFields = cl.getDeclaredFields();
+ for (int i = 0; i < originFields.length; i++) {
+ Field field = originFields[i];
+ if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+
+ if ("format".equals(field.getName())) {
+ continue;
+ }
+ fields.add(field);
+ }
+ }
+ return fields.toArray(new Field[0]);
+ }
+}
diff --git a/src/main/java/com/caucho/hessian/io/throwable/ThrowableDeserializer.java b/src/main/java/com/caucho/hessian/io/throwable/ThrowableDeserializer.java
new file mode 100644
index 0000000..9d87a01
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/throwable/ThrowableDeserializer.java
@@ -0,0 +1,327 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.AbstractFieldAdaptorDeserializer;
+import com.caucho.hessian.io.AbstractHessianInput;
+import com.caucho.hessian.io.HessianFieldException;
+import com.caucho.hessian.io.IOExceptionWrapper;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * @author junyuan
+ * @version ThrowableDeserializer.java, v 0.1 2023年04月10日 20:37 junyuan Exp $
+ */
+public class ThrowableDeserializer extends AbstractFieldAdaptorDeserializer {
+
+ private final Class> _type;
+ protected Method addSuppressed = null;
+
+ private final Throwable selfRef = new Throwable();
+
+ public ThrowableDeserializer(Class cl) {
+ super(cl);
+ _type = cl;
+
+ try {
+ // since 1.7
+ addSuppressed = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class);
+ } catch (NoSuchMethodException e) {
+
+ }
+ }
+
+ @Override
+ public Class getType() {
+ return _type;
+ }
+
+ @Override
+ public Object readObject(AbstractHessianInput in, String[] fieldNames) throws IOException {
+ try {
+ int ref = in.addRef(selfRef);
+ Map fieldValueMap = readField(in, fieldNames);
+ Throwable obj = instantiate(_type, fieldValueMap);
+ fillFields(_type, obj, fieldValueMap);
+ in.setRef(ref, obj);
+ return obj;
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IOExceptionWrapper(Throwable.class.getName() + ":" + e, e);
+ }
+ }
+
+ protected Map readField(AbstractHessianInput in, String[] fieldNames)
+ throws IOException {
+ Map fieldValueMap = new HashMap();
+ for (int i = 0; i < fieldNames.length; i++) {
+ String name = fieldNames[i];
+ Field field = _fields.get(name);
+ if (field == null) {
+ continue;
+ }
+
+ if (String.class.equals(field.getType())) {
+ fieldValueMap.put(name, in.readString());
+ } else {
+ fieldValueMap.put(name, in.readObject());
+ }
+ }
+ return fieldValueMap;
+ }
+
+ protected Throwable instantiate(Class> clazz, Map fieldValueMap)
+ throws Exception {
+ Throwable ex = null;
+ try {
+ ex = doInstantiate(clazz, fieldValueMap);
+ } catch (Exception instantiateException) {
+ // todo: unsafe
+ } finally {
+ if (ex == null) {
+ // 兜底返回 Throwable
+ ex = new Throwable((String) fieldValueMap.get("detailMessage"), (Throwable) fieldValueMap.get("cause"));
+ }
+ }
+ return ex;
+ }
+
+ protected void fillFields(Class> clazz, Throwable obj, Map valueMap)
+ throws IOException {
+ for (String key : valueMap.keySet()) {
+ Object value = valueMap.get(key);
+ if (value == null)
+ continue;
+
+ if (key.equals("cause")) {
+ // 如果 cause 还未被写入, init
+ if (value.equals(selfRef)) {
+ // 如果 cause 是自己, 跳过不写
+ continue;
+ }
+
+ if (obj.getCause() == null) {
+ try {
+ obj.initCause((Throwable) value);
+ } catch (Exception e) {
+ logDeserializeError(_fields.get(key), value, e);
+ }
+ }
+ }
+ else if (key.equals("suppressedExceptions")) {
+ // since 1.7
+ try {
+ if (!(value instanceof List)) {
+ continue;
+ }
+ List listValue = (List) value;
+ if (listValue.size() == 0) {
+ continue;
+ }
+ if (addSuppressed != null) {
+ for (Object item : listValue) {
+ addSuppressed.invoke(obj, item);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ else if (key.equals("stackTrace")) {
+ obj.setStackTrace((StackTraceElement[]) value);
+ }
+ else if (key.equals("detailMessage")) {
+ // 只能通过构造方法写入
+ }
+ // 其他所有 field
+ else {
+ fillOtherFields(clazz, obj, key, value);
+ }
+ }
+ }
+
+ protected void fillOtherFields(Class> clazz, Throwable obj, String key, Object value)
+ throws IOException {
+ Field field = _fields.get(key);
+ if (field == null) {
+ return;
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(obj, value);
+ } catch (Exception e) {
+ logDeserializeError(field, value, e);
+ }
+ }
+
+ /**
+ * 实例化, 这里只会返回 targetClass 的实例对象或者 null
+ * 根据优先级分别使用构造函数
+ * ExceptionClass(String message, Throwable cause)
+ * ExceptionClass(String message)
+ * ExceptionClass()
+ * ExceptionClass(args...) 无法确认参数, 使用默认值进行构造, 优先级最低
+ *
+ * @param clazz
+ * @param fieldValueMap
+ * @return
+ * @throws Exception
+ */
+ private Throwable doInstantiate(Class> clazz, Map fieldValueMap)
+ throws Exception {
+ Constructor> causeConstructor = null;
+ Constructor> messageConstructor = null;
+ Constructor> defaultConstructor = null;
+ Constructor> constructorByCost = null;
+
+ long bestCost = Long.MAX_VALUE;
+ // 只会返回public的构造方法
+ for (Constructor> c : clazz.getDeclaredConstructors()) {
+ Class>[] pTypes = c.getParameterTypes();
+
+ if (pTypes.length == 0) {
+ defaultConstructor = c;
+ continue;
+ }
+
+ if (pTypes.length == 1 && pTypes[0].equals(String.class)) {
+ // Exception(String detailMessage)
+ messageConstructor = c;
+ continue;
+ }
+
+ if (pTypes.length == 2 && pTypes[0].equals(String.class) && pTypes[1].equals(Throwable.class)) {
+ // Exception(String detailMessage, Throwable cause)
+ causeConstructor = c;
+ continue;
+ }
+
+ // 对于不是以上三种的构造方法, 根据JavaDeserializer的cost计算方式获取constructor
+ if (calculateCost(pTypes) < bestCost) {
+ constructorByCost = c;
+ }
+ }
+
+ // 根据优先级调用
+ String detailMessage = (String) fieldValueMap.get("detailMessage");
+ Throwable cause = (Throwable) fieldValueMap.get("cause");
+ if (causeConstructor != null) {
+ return (Throwable) causeConstructor.newInstance(detailMessage, cause);
+ }
+ if (messageConstructor != null) {
+ return (Throwable) messageConstructor.newInstance(detailMessage);
+ }
+ if (defaultConstructor != null) {
+ return (Throwable) defaultConstructor.newInstance();
+ }
+ if (constructorByCost != null) {
+ Object[] args = getConstructorArgs(constructorByCost);
+ return (Throwable) constructorByCost.newInstance(args);
+ }
+
+ return null;
+ }
+
+ /**
+ * get default arg value
+ * @param c
+ * @return
+ */
+ protected Object[] getConstructorArgs(Constructor c) {
+ Class>[] pTypes = c.getParameterTypes();
+ Object[] constructorArgs = new Object[pTypes.length];
+ for (int i = 0; i < pTypes.length; i++) {
+ constructorArgs[i] = getParamArg(pTypes[i]);
+ }
+ return constructorArgs;
+ }
+
+ /**
+ * ref to {@link com.caucho.hessian.io.JavaDeserializer#JavaDeserializer(java.lang.Class)}
+ * @param pTypes
+ * @return
+ */
+ protected long calculateCost(Class>[] pTypes) {
+ long cost = 0;
+
+ for (int j = 0; j < pTypes.length; j++) {
+ cost = 4 * cost;
+
+ if (Object.class.equals(pTypes[j]))
+ cost += 1;
+ else if (String.class.equals(pTypes[j]))
+ cost += 2;
+ else if (int.class.equals(pTypes[j]))
+ cost += 3;
+ else if (long.class.equals(pTypes[j]))
+ cost += 4;
+ else if (pTypes[j].isPrimitive())
+ cost += 5;
+ else
+ cost += 6;
+ }
+
+ if (cost < 0 || cost > (1 << 48))
+ cost = 1 << 48;
+
+ cost += pTypes.length << 48;
+ return cost;
+ }
+
+ /**
+ * ref to {@link com.caucho.hessian.io.JavaDeserializer#JavaDeserializer(java.lang.Class)}
+ * @param cl
+ * @return
+ */
+ protected Object getParamArg(Class cl) {
+ if (!cl.isPrimitive())
+ return null;
+ else if (boolean.class.equals(cl))
+ return Boolean.FALSE;
+ else if (byte.class.equals(cl))
+ return new Byte((byte) 0);
+ else if (short.class.equals(cl))
+ return new Short((short) 0);
+ else if (char.class.equals(cl))
+ return new Character((char) 0);
+ else if (int.class.equals(cl))
+ return Integer.valueOf(0);
+ else if (long.class.equals(cl))
+ return Long.valueOf(0);
+ else if (float.class.equals(cl))
+ return Float.valueOf(0);
+ else if (double.class.equals(cl))
+ return Double.valueOf(0);
+ else
+ throw new UnsupportedOperationException();
+ }
+
+ private void logDeserializeError(Field field, Object value, Throwable e) throws IOException {
+ String fieldName = (field.getDeclaringClass().getName()
+ + "." + field.getName());
+
+ if (e instanceof HessianFieldException)
+ throw (HessianFieldException) e;
+ else if (e instanceof IOException)
+ throw new HessianFieldException(fieldName + ": " + e.getMessage(), e);
+
+ if (value != null)
+ throw new HessianFieldException(fieldName + ": " + value.getClass().getName()
+ + " cannot be assigned to " + field.getType().getName());
+ else
+ throw new HessianFieldException(fieldName + ": " + field.getType().getName() +
+ " cannot be assigned from null", e);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/caucho/hessian/io/throwable/ThrowableHelper.java b/src/main/java/com/caucho/hessian/io/throwable/ThrowableHelper.java
new file mode 100644
index 0000000..8f682fc
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/throwable/ThrowableHelper.java
@@ -0,0 +1,60 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.AbstractDeserializer;
+import com.caucho.hessian.io.AbstractSerializer;
+import com.caucho.hessian.io.JavaDeserializer;
+import com.caucho.hessian.io.JavaSerializer;
+import com.caucho.hessian.io.throwable.adapter.EnumConstantNotPresentExceptionDeserializer;
+import com.caucho.hessian.io.throwable.adapter.EnumConstantNotPresentExceptionSerializer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author junyuan
+ * @version ThrowableHelper.java, v 0.1 2023年04月27日 16:56 junyuan Exp $
+ */
+public class ThrowableHelper {
+
+ private static final boolean isLessThanJdk17 = isLessThanJdk17();
+
+ private static boolean isLessThanJdk17() {
+ String javaVersion = System.getProperty("java.specification.version");
+ return Double.parseDouble(javaVersion) < 17;
+ }
+
+ public static AbstractDeserializer getDeserializer(Class> cl) {
+ if (isLessThanJdk17) {
+ return new JavaDeserializer(cl);
+ }
+ if (EnumConstantNotPresentException.class.isAssignableFrom(cl)) {
+ return new EnumConstantNotPresentExceptionDeserializer(cl);
+ }
+
+ return new ThrowableDeserializer(cl);
+ }
+
+ public static AbstractSerializer getSerializer(Class> cl) {
+ if (isLessThanJdk17) {
+ return new ReflectThrowableSerializer(cl);
+ }
+
+ if (throwableSerializerMap.containsKey(cl.getName())) {
+ return throwableSerializerMap.get(cl.getName());
+ }
+
+ return new ThrowableSerializer(cl);
+ }
+
+ private static final Map throwableSerializerMap = new HashMap();
+ static {
+ throwableSerializerMap.put(EnumConstantNotPresentException.class.getName(),
+ new EnumConstantNotPresentExceptionSerializer());
+ }
+
+}
diff --git a/src/main/java/com/caucho/hessian/io/throwable/ThrowableSerializer.java b/src/main/java/com/caucho/hessian/io/throwable/ThrowableSerializer.java
new file mode 100644
index 0000000..b66d968
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/throwable/ThrowableSerializer.java
@@ -0,0 +1,112 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.AbstractHessianOutput;
+import com.caucho.hessian.io.AbstractFieldAdaptorSerializer;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author junyuan
+ * @version ThrowableSerializer.java, v 0.1 2023年04月10日 19:30 junyuan Exp $
+ */
+public class ThrowableSerializer extends AbstractFieldAdaptorSerializer {
+
+ protected static final Logger log = Logger.getLogger(ThrowableSerializer.class.getName());
+
+ protected Method getSuppressed = null;
+
+ public ThrowableSerializer(Class> clazz) {
+ super(clazz);
+
+ try {
+ getSuppressed = clazz.getMethod("getSuppressed");
+ } catch (NoSuchMethodException e) {
+
+ }
+ }
+
+ @Override
+ public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
+ // 如果需要反射操作获取 stack trace, 这里需要先 get 一下
+ ((Throwable) obj).getStackTrace();
+
+ super.writeObject(obj, out);
+ }
+
+ @Override
+ protected void serializeField(AbstractHessianOutput out, Object obj, Field field)
+ throws IOException {
+ if (!(obj instanceof Throwable)) {
+ throw new UnsupportedOperationException(String.valueOf(this));
+ }
+
+ Throwable current = (Throwable) obj;
+
+ if ("detailMessage".equals(field.getName())) {
+ out.writeString(current.getMessage());
+ }
+ else if ("cause".equals(field.getName())) {
+ out.writeObject(current.getCause());
+ }
+ else if ("stackTrace".equals(field.getName())) {
+ out.writeObject(current.getStackTrace());
+ }
+ else if ("suppressedExceptions".equals(field.getName())) {
+ if (getSuppressed == null) {
+ throw new UnsupportedOperationException(String.valueOf(this));
+ }
+ Throwable[] throwableArray;
+ try {
+ throwableArray = (Throwable[]) getSuppressed.invoke(obj);
+ } catch (Exception e) {
+ throw new UnsupportedOperationException(e);
+ }
+
+ List throwableList;
+ if (throwableArray.length == 0) {
+ // 旧版通过反射会获取到这个类型
+ throwableList = Collections.unmodifiableList(new ArrayList());
+ } else {
+ throwableList = new ArrayList(Arrays.asList(throwableArray));
+ }
+
+ out.writeObject(throwableList);
+ }
+ else {
+ defaultSerializeField(out, obj, field);
+ }
+
+ }
+
+ /**
+ * 针对自定义的 field, 尝试以反射方式获取
+ * @param out
+ * @param obj
+ * @param field
+ * @throws IOException
+ */
+ protected void defaultSerializeField(AbstractHessianOutput out, Object obj, Field field)
+ throws IOException {
+ Object fieldValue = null;
+ try {
+ field.setAccessible(true);
+ fieldValue = field.get(obj);
+ } catch (IllegalAccessException e) {
+ log.log(Level.FINE, e.toString());
+ }
+ out.writeObject(fieldValue);
+ }
+}
diff --git a/src/main/java/com/caucho/hessian/io/throwable/adapter/EnumConstantNotPresentExceptionDeserializer.java b/src/main/java/com/caucho/hessian/io/throwable/adapter/EnumConstantNotPresentExceptionDeserializer.java
new file mode 100644
index 0000000..618611d
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/throwable/adapter/EnumConstantNotPresentExceptionDeserializer.java
@@ -0,0 +1,28 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.throwable.adapter;
+
+import com.caucho.hessian.io.throwable.ThrowableDeserializer;
+
+import java.util.Map;
+
+/**
+ *
+ * @author junyuan
+ * @version EnumConstantNotPresentExceptionDeserializer.java, v 0.1 2023年04月27日 17:55 junyuan Exp $
+ */
+public class EnumConstantNotPresentExceptionDeserializer extends ThrowableDeserializer {
+ public EnumConstantNotPresentExceptionDeserializer(Class cl) {
+ super(cl);
+ }
+
+ @Override
+ protected Throwable instantiate(Class> clazz, Map fieldValueMap)
+ throws Exception {
+ Class enumType = (Class) fieldValueMap.remove("enumType");
+ String constantName = (String) fieldValueMap.remove("constantName");
+ return new EnumConstantNotPresentException(enumType, constantName);
+ }
+}
diff --git a/src/main/java/com/caucho/hessian/io/throwable/adapter/EnumConstantNotPresentExceptionSerializer.java b/src/main/java/com/caucho/hessian/io/throwable/adapter/EnumConstantNotPresentExceptionSerializer.java
new file mode 100644
index 0000000..cb1e505
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/io/throwable/adapter/EnumConstantNotPresentExceptionSerializer.java
@@ -0,0 +1,41 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.io.throwable.adapter;
+
+import com.caucho.hessian.io.AbstractHessianOutput;
+import com.caucho.hessian.io.throwable.ThrowableSerializer;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+/**
+ *
+ * @author junyuan
+ * @version EnumConstantNotPresentExceptionSerializer.java, v 0.1 2023年04月27日 17:56 junyuan Exp $
+ */
+public class EnumConstantNotPresentExceptionSerializer extends ThrowableSerializer {
+
+ public EnumConstantNotPresentExceptionSerializer() {
+ super(EnumConstantNotPresentException.class);
+ }
+
+ @Override
+ protected void defaultSerializeField(AbstractHessianOutput out, Object obj, Field field)
+ throws IOException {
+ if (!(obj instanceof EnumConstantNotPresentException)) {
+ throw new UnsupportedOperationException(String.valueOf(this));
+ }
+
+ EnumConstantNotPresentException cast = (EnumConstantNotPresentException) obj;
+
+ if (field.getName().equals("enumType")) {
+ out.writeObject(cast.enumType());
+ } else if (field.getName().equals("constantName")) {
+ out.writeString(cast.constantName());
+ } else {
+ super.defaultSerializeField(out, obj, field);
+ }
+ }
+}
diff --git a/src/main/java/com/caucho/hessian/util/ReflectionUtil.java b/src/main/java/com/caucho/hessian/util/ReflectionUtil.java
new file mode 100644
index 0000000..00bf050
--- /dev/null
+++ b/src/main/java/com/caucho/hessian/util/ReflectionUtil.java
@@ -0,0 +1,97 @@
+/*
+ * Ant Group
+ * Copyright (c) 2004-2023 All Rights Reserved.
+ */
+package com.caucho.hessian.util;
+
+import org.slf4j.Logger;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ *
+ * @author junyuan
+ * @version ReflectionUtil.java, v 0.1 2023年03月30日 21:23 junyuan Exp $
+ */
+public class ReflectionUtil {
+ private static org.slf4j.Logger LOGGER = judgeLogger();
+
+ //do not change this
+ public static final String HESSIAN_SERIALIZE_LOG_NAME = "HessianSerializeLog";
+ public static final String CONFIG_LOG_SPACE_NAME = "com.alipay.sofa.hessian";
+
+ private static Logger judgeLogger() {
+
+ try {
+ ReflectionUtil.class.getClassLoader().loadClass("com.alipay.sofa.common.log.LoggerSpaceManager");
+ } catch (Throwable e) {
+ //do nothing
+ return null;
+ }
+
+ return com.alipay.sofa.common.log.LoggerSpaceManager.getLoggerBySpace(HESSIAN_SERIALIZE_LOG_NAME,
+ CONFIG_LOG_SPACE_NAME);
+ }
+
+ public static boolean setAccessible(Method m) {
+ m.setAccessible(true);
+ return true;
+ }
+
+ public static boolean setAccessible(Constructor c) {
+ c.setAccessible(true);
+ return true;
+ }
+
+ public static boolean setAccessible(Field f) {
+ f.setAccessible(true);
+ return true;
+ }
+
+ public static boolean trySetAccessible(Method m) {
+ try {
+ m.setAccessible(true);
+ } catch (Throwable t) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER
+ .debug(
+ "failed when setting accessible on method [" + m.toString() + "], error message: " +
+ t.getMessage(), t);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean trySetAccessible(Constructor c) {
+ try {
+ c.setAccessible(true);
+ } catch (Throwable t) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER
+ .debug(
+ "failed when setting accessible on method [" + c.toString() + "], error message: " +
+ t.getMessage(), t);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean trySetAccessible(Field f) {
+ try {
+ f.setAccessible(true);
+ } catch (Throwable t) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER
+ .debug(
+ "failed when setting accessible on method [" + f.toString() + "], error message: " +
+ t.getMessage(), t);
+ }
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/caucho/hessian/io/java17/base/CurrencyWrapper.java b/src/test/java/com/caucho/hessian/io/java17/base/CurrencyWrapper.java
new file mode 100644
index 0000000..24c5099
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/java17/base/CurrencyWrapper.java
@@ -0,0 +1,68 @@
+/*
+ * 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 com.caucho.hessian.io.java17.base;
+
+import java.io.Serializable;
+import java.util.Currency;
+
+/**
+ *
+ * @author junyuan
+ * @version CurrencyWrapper.java, v 0.1 2023年08月09日 11:51 junyuan Exp $
+ */
+public class CurrencyWrapper implements Serializable {
+ private static final long serialVersionUID = 6738644291381453889L;
+
+ private int cent;
+
+ private Currency currency;
+
+ public CurrencyWrapper() {
+ }
+
+ public CurrencyWrapper(Currency currency) {
+ this.cent = currency.getCurrencyCode().hashCode();
+ this.currency = currency;
+ }
+
+ /**
+ * Getter method for property currency.
+ *
+ * @return property value of currency
+ */
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ /**
+ * Setter method for property currency.
+ *
+ * @param currency value to be assigned to property currency
+ */
+ public void setCurrency(Currency currency) {
+ this.currency = currency;
+ }
+
+ /**
+ * Getter method for property cent.
+ *
+ * @return property value of cent
+ */
+ public int getCent() {
+ return cent;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/caucho/hessian/io/java17/base/SerializeCompatibleTest.java b/src/test/java/com/caucho/hessian/io/java17/base/SerializeCompatibleTest.java
new file mode 100644
index 0000000..57b5212
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/java17/base/SerializeCompatibleTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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 com.caucho.hessian.io.java17.base;
+
+import com.caucho.hessian.io.Hessian2Input;
+import com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.io.SerializerFactory;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Currency;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * 在 jdk8 下模拟运行 jdk17 下用的序列化器
+ *
+ * @author junyuan
+ * @version SerializeCompatibleTest.java, v 0.1 2023年08月09日 15:52 junyuan Exp $
+ */
+public class SerializeCompatibleTest {
+ // 把 currency 的去掉了
+ private static SerializerFactory originFactory;
+ // 把 currency 的加上
+ private static SerializerFactory factory;
+ private static ByteArrayOutputStream os;
+
+ private static final boolean isLessThanJdk17 = isLessThanJdk17();
+
+ private static boolean isLessThanJdk17() {
+ String javaVersion = System.getProperty("java.specification.version");
+ return Double.parseDouble(javaVersion) < 17;
+ }
+
+ @BeforeClass
+ public static void setUp() {
+ factory = new SerializeFactoryWithCurrency();
+ originFactory = new SerializeFactoryWithoutCurrency();
+
+ os = new ByteArrayOutputStream();
+ }
+
+ /**
+ * Wrapper
+ * use currencySerializer to encode and java serializer to decode
+ * 'cause' is bound to lost here as getCause may return null
+ */
+ @Test
+ public void test_case_1() {
+ if (isLessThanJdk17) {
+ try {
+ test_JavaCurrencyWrapper(factory, originFactory);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.assertNull(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * wrapper
+ * use java serializer to encode and currencySerializer to decode
+ * @throws IOException
+ */
+ @Test
+ public void test_case_2() {
+ if (isLessThanJdk17) {
+ try {
+ test_JavaCurrencyWrapper(originFactory, factory);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.assertNull(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ *
+ * @throws IOException
+ */
+ @Test
+ public void test_case_3() {
+ if (isLessThanJdk17) {
+ try {
+ test_JavaCurrencyDirectly(factory, originFactory);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.assertNull(e.getMessage(), e);
+ }
+ }
+ }
+
+ @Test
+ public void test_case_4() {
+ if (isLessThanJdk17) {
+ try {
+ test_JavaCurrencyDirectly(originFactory, factory);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.assertNull(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * object list
+ * @throws IOException
+ */
+ @Test
+ public void test_case_5() {
+ if (isLessThanJdk17()) {
+ try {
+ test_JavaCurrencyWrapperList(factory, factory);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.assertNull(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * 确保 CurrencySerializer 和 JavaSerialize 序列化的产物完全一致
+ * @throws IOException
+ */
+ @Test
+ public void test_bytes_equals() {
+ if (isLessThanJdk17()) {
+ try {
+ test_Serialize(originFactory, factory);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.assertNull(e.getMessage(), e);
+ }
+ }
+ }
+
+ @Test
+ public void test_decode_encode_jdk17() {
+ if (!isLessThanJdk17()) {
+ try {
+ test_JavaCurrencyWrapper(factory, factory);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.assertNull(e.getMessage(), e);
+ }
+ }
+
+ }
+
+ protected Object doEncodeNDecode(Object origin, SerializerFactory serializerFactory,
+ SerializerFactory deserializerFactory) throws IOException {
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+
+ output.setSerializerFactory(serializerFactory);
+ output.writeObject(origin);
+ output.flush();
+
+ ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+ Hessian2Input input = new Hessian2Input(is);
+ input.setSerializerFactory(deserializerFactory);
+ Object actual = input.readObject();
+ return actual;
+ }
+
+ private void test_JavaCurrencyWrapper(SerializerFactory serialize, SerializerFactory deserialize)
+ throws IOException {
+ if (isLessThanJdk17()) {
+ CurrencyWrapper cw = new CurrencyWrapper();
+ cw.setCurrency(Currency.getInstance(Locale.SIMPLIFIED_CHINESE));
+
+ Object result = doEncodeNDecode(cw, serialize, deserialize);
+ Assert.assertTrue(result instanceof CurrencyWrapper);
+ Currency newC = ((CurrencyWrapper) result).getCurrency();
+ Assert.assertEquals(cw.getCurrency(), newC);
+ }
+ }
+
+ private void test_JavaCurrencyDirectly(SerializerFactory serialize, SerializerFactory deserialize)
+ throws IOException {
+ if (isLessThanJdk17()) {
+ Currency origin = Currency.getInstance(Locale.SIMPLIFIED_CHINESE);
+
+ Object result = doEncodeNDecode(origin, serialize, deserialize);
+ Assert.assertTrue(result instanceof Currency);
+ Assert.assertEquals(origin, result);
+ }
+ }
+
+ private void test_JavaCurrencyWrapperList(SerializerFactory serialize, SerializerFactory deserialize)
+ throws IOException {
+ if (isLessThanJdk17()) {
+ List cl = new ArrayList();
+ cl.add(new CurrencyWrapper(Currency.getInstance(Locale.getAvailableLocales()[10])));
+ cl.add(new CurrencyWrapper(Currency.getInstance(Locale.getAvailableLocales()[10])));
+ cl.add(new CurrencyWrapper(Currency.getInstance(Locale.getAvailableLocales()[41])));
+
+ Object result = doEncodeNDecode(cl, serialize, deserialize);
+ Assert.assertTrue(result instanceof List);
+
+ Assert.assertEquals(cl.get(0).getCurrency(), ((CurrencyWrapper) ((List>) result).get(0)).getCurrency());
+ Assert.assertEquals(cl.get(1).getCurrency(), ((CurrencyWrapper) ((List>) result).get(1)).getCurrency());
+ Assert.assertEquals(cl.get(2).getCurrency(), ((CurrencyWrapper) ((List>) result).get(2)).getCurrency());
+ }
+ }
+
+ private void test_Serialize(SerializerFactory originFactory, SerializerFactory newFactory)
+ throws IOException {
+ CurrencyWrapper cw = new CurrencyWrapper();
+ cw.setCurrency(Currency.getInstance(Locale.SIMPLIFIED_CHINESE));
+
+ byte[] resultOrigin = doSerialize(cw, originFactory);
+ byte[] resultNew = doSerialize(cw, factory);
+ Assert.assertTrue(bytesEquals(resultOrigin, resultNew));
+ }
+
+ private byte[] doSerialize(Object o, SerializerFactory factory) throws IOException {
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+
+ output.setSerializerFactory(originFactory);
+ output.writeObject(o);
+ output.flush();
+
+ return os.toByteArray();
+ }
+
+ protected boolean bytesEquals(byte[] src, byte[] target) {
+ if (src == null && target == null) {
+ return true;
+ }
+
+ if (src == null || target == null) {
+ return false;
+ }
+
+ if (src.length != target.length) {
+ return false;
+ }
+
+ for (int i = 0; i < src.length; i++) {
+ if (src[i] != target[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/test/java/com/caucho/hessian/io/java17/base/SerializeFactoryWithCurrency.java b/src/test/java/com/caucho/hessian/io/java17/base/SerializeFactoryWithCurrency.java
new file mode 100644
index 0000000..26648f4
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/java17/base/SerializeFactoryWithCurrency.java
@@ -0,0 +1,51 @@
+/*
+ * 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 com.caucho.hessian.io.java17.base;
+
+import com.caucho.hessian.io.Deserializer;
+import com.caucho.hessian.io.HessianProtocolException;
+import com.caucho.hessian.io.Serializer;
+import com.caucho.hessian.io.SerializerFactory;
+
+import java.util.Currency;
+
+/**
+ *
+ * @author junyuan
+ * @version SerializeFactoryWithoutCurrency.java, v 0.1 2023年08月09日 11:48 junyuan Exp $
+ */
+public class SerializeFactoryWithCurrency extends SerializerFactory {
+
+ private Serializer javaCurrencySerializer = new JavaCurrencySerializer(Currency.class);
+ private Deserializer javaCurrencyDeserializer = new JavaCurrencyDeserializer();
+
+ @Override
+ public Serializer getSerializer(Class cl) throws HessianProtocolException {
+ if (Currency.class.equals(cl)) {
+ return javaCurrencySerializer;
+ }
+ return super.getSerializer(cl);
+ }
+
+ @Override
+ public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
+ if (Currency.class.equals(cl)) {
+ return javaCurrencyDeserializer;
+ }
+ return super.getDeserializer(cl);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/caucho/hessian/io/java17/base/SerializeFactoryWithoutCurrency.java b/src/test/java/com/caucho/hessian/io/java17/base/SerializeFactoryWithoutCurrency.java
new file mode 100644
index 0000000..e8ffdbd
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/java17/base/SerializeFactoryWithoutCurrency.java
@@ -0,0 +1,50 @@
+/*
+ * 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 com.caucho.hessian.io.java17.base;
+
+import com.caucho.hessian.io.Deserializer;
+import com.caucho.hessian.io.HessianProtocolException;
+import com.caucho.hessian.io.JavaDeserializer;
+import com.caucho.hessian.io.JavaSerializer;
+import com.caucho.hessian.io.Serializer;
+import com.caucho.hessian.io.SerializerFactory;
+
+import java.util.Currency;
+
+/**
+ *
+ * @author junyuan
+ * @version SerializeFactoryWithoutCurrency.java, v 0.1 2023年08月09日 11:48 junyuan Exp $
+ */
+public class SerializeFactoryWithoutCurrency extends SerializerFactory {
+
+ @Override
+ public Serializer getSerializer(Class cl) throws HessianProtocolException {
+ if (Currency.class.equals(cl)) {
+ return new JavaSerializer(cl);
+ }
+ return super.getSerializer(cl);
+ }
+
+ @Override
+ public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
+ if (Currency.class.equals(cl)) {
+ return new JavaDeserializer(cl);
+ }
+ return super.getDeserializer(cl);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/caucho/hessian/io/throwable/ExceptionWrapper.java b/src/test/java/com/caucho/hessian/io/throwable/ExceptionWrapper.java
new file mode 100644
index 0000000..e4c7d79
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/throwable/ExceptionWrapper.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.caucho.hessian.io.throwable;
+
+import java.io.Serializable;
+
+/**
+ *
+ * @author junyuan
+ * @version ExceptionWrapper.java, v 0.1 2023年04月10日 14:41 junyuan Exp $
+ */
+public class ExceptionWrapper implements Serializable {
+ private static final long serialVersionUID = 4065571790594438646L;
+
+ Throwable t;
+
+ /**
+ * Getter method for property t.
+ *
+ * @return property value of t
+ */
+ public Throwable getT() {
+ return t;
+ }
+
+ /**
+ * Setter method for property t.
+ *
+ * @param t value to be assigned to property t
+ */
+ public void setT(Throwable t) {
+ this.t = t;
+ }
+
+}
diff --git a/src/test/java/com/caucho/hessian/io/throwable/JDK17SerializeFactory.java b/src/test/java/com/caucho/hessian/io/throwable/JDK17SerializeFactory.java
new file mode 100644
index 0000000..c75f4d2
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/throwable/JDK17SerializeFactory.java
@@ -0,0 +1,71 @@
+/*
+ * 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 com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.Deserializer;
+import com.caucho.hessian.io.HessianProtocolException;
+import com.caucho.hessian.io.Serializer;
+import com.caucho.hessian.io.SerializerFactory;
+import com.caucho.hessian.io.throwable.adapter.EnumConstantNotPresentExceptionDeserializer;
+import com.caucho.hessian.io.throwable.adapter.EnumConstantNotPresentExceptionSerializer;
+
+/**
+ * 可以在 java8 环境下运行专门给 jdk17 使用的序列化器
+ * 以便在 java8 下进行兼容测试
+ * @author junyuan
+ * @version JDK17SerializeFactory.java, v 0.1 2023年05月06日 11:13 junyuan Exp $
+ */
+public class JDK17SerializeFactory extends SerializerFactory {
+ @Override
+ public Serializer getSerializer(Class cl) throws HessianProtocolException {
+ Serializer serializer = super.getSerializer(cl);
+
+ if (Throwable.class.isAssignableFrom(cl)) {
+ if (EnumConstantNotPresentException.class.equals(cl)) {
+ serializer = new EnumConstantNotPresentExceptionSerializer();
+ } else {
+ serializer = new ThrowableSerializer(cl);
+ }
+ }
+
+ if (StackTraceElement.class.isAssignableFrom(cl)) {
+ serializer = new StackTraceElementSerializer();
+ }
+ _cachedSerializerMap.put(cl, serializer);
+
+ return serializer;
+ }
+
+ @Override
+ public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
+ Deserializer deserializer = super.getDeserializer(cl);
+
+ if (Throwable.class.isAssignableFrom(cl)) {
+ if (EnumConstantNotPresentException.class.equals(cl)) {
+ deserializer = new EnumConstantNotPresentExceptionDeserializer(cl);
+ } else {
+ deserializer = new ThrowableDeserializer(cl);
+ }
+ }
+
+ if (StackTraceElement.class.isAssignableFrom(cl))
+ deserializer = new StackTraceElementDeserializer();
+
+ _cachedDeserializerMap.put(cl, deserializer);
+ return deserializer;
+ }
+}
diff --git a/src/test/java/com/caucho/hessian/io/throwable/MeaninglessEnum.java b/src/test/java/com/caucho/hessian/io/throwable/MeaninglessEnum.java
new file mode 100644
index 0000000..6431772
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/throwable/MeaninglessEnum.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.caucho.hessian.io.throwable;
+
+/**
+ *
+ * @author junyuan
+ * @version MeaninglessEnum.java, v 0.1 2023年04月27日 19:21 junyuan Exp $
+ */
+public enum MeaninglessEnum {
+ S1,
+ S2,
+ S3
+}
diff --git a/src/test/java/com/caucho/hessian/io/throwable/SelfDefinedException.java b/src/test/java/com/caucho/hessian/io/throwable/SelfDefinedException.java
new file mode 100644
index 0000000..3b06bd5
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/throwable/SelfDefinedException.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.caucho.hessian.io.throwable;
+
+/**
+ *
+ * @author junyuan
+ * @version SelfDefinedException.java, v 0.1 2023年04月11日 10:12 junyuan Exp $
+ */
+public class SelfDefinedException extends RuntimeException {
+
+ private String bizCode;
+
+ private String bizMessage;
+
+ public SelfDefinedException(String bizCode, String bizMessage) {
+ this.bizCode = bizCode;
+ this.bizMessage = bizMessage;
+ }
+
+}
diff --git a/src/test/java/com/caucho/hessian/io/throwable/SerializeCompatibleSelfTest.java b/src/test/java/com/caucho/hessian/io/throwable/SerializeCompatibleSelfTest.java
new file mode 100644
index 0000000..8d71e3c
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/throwable/SerializeCompatibleSelfTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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 com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.io.SerializerFactory;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * jdk17 下的throwable 序列化必定有损, 这个类仅用于自测, 实际运行必然不通过
+ *
+ * @author junyuan
+ * @version SerializeCompatibleSelfTest.java, v 0.1 2023年04月10日 14:52 junyuan Exp $
+ */
+public class SerializeCompatibleSelfTest {
+
+ private static SerializerFactory useJdk17Factory;
+ private static SerializerFactory originFactory;
+ private static SerializerFactory factory;
+ private static ByteArrayOutputStream os;
+
+ private Method addSuppressed = null;
+ private Method getSuppressed = null;
+ {
+ try {
+ addSuppressed = Throwable.class.getMethod("addSuppressed", Throwable.class);
+ getSuppressed = Throwable.class.getMethod("getSuppressed");
+ } catch (Exception e) {
+
+ }
+ }
+
+ Throwable t = null;
+ {
+ Throwable x = null;
+ try {
+ t.getStackTrace();
+ } catch (NullPointerException e) {
+ t = e;
+ }
+
+ t = new EnumConstantNotPresentException(MeaninglessEnum.class, "CIG");
+
+ try {
+ addSuppressed.invoke(t, x);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ private static final boolean isLessThanJdk17 = isLessThanJdk17();
+
+ private static boolean isLessThanJdk17() {
+ String javaVersion = System.getProperty("java.specification.version");
+ return Double.parseDouble(javaVersion) < 17;
+ }
+
+ @BeforeClass
+ public static void setUp() {
+ factory = new SerializerFactory();
+ originFactory = new SerializeFactoryWithoutThrowable();
+ // 可以在jdk8环境下使用到专门为jdk17准备的 serializer
+ useJdk17Factory = new JDK17SerializeFactory();
+
+ os = new ByteArrayOutputStream();
+ }
+
+ /**
+ * result byte should be in same with former version so as to behaving compatible
+ * @throws IOException
+ */
+ // @Test
+ public void test_EnumConstantNotPresentExceptionSerialize() throws IOException {
+ if (isLessThanJdk17) {
+ // jdk 17 开始不需要执行这个, 反射受限, 执行会失败
+ byte[] caseOrigin = serializeEnumConstantNotPresentException(originFactory);
+ byte[] caseNew = serializeEnumConstantNotPresentException(factory);
+ byte[] caseJdk17 = serializeEnumConstantNotPresentException(useJdk17Factory);
+ Assert.assertTrue(bytesEquals(caseOrigin, caseNew));
+ Assert.assertTrue(bytesEquals(caseJdk17, caseNew));
+ }
+
+ }
+
+ // @Test
+ public void test_EnumConstantNotPresentExceptionWrapperSerialize() throws IOException {
+ if (isLessThanJdk17) {
+ byte[] wrapperCaseOrigin = serializeEnumConstantNotPresentExceptionWrapper(originFactory);
+ byte[] wrapperCaseNew = serializeEnumConstantNotPresentExceptionWrapper(factory);
+ byte[] wrapperCaseJdk17 = serializeEnumConstantNotPresentExceptionWrapper(useJdk17Factory);
+ Assert.assertTrue(bytesEquals(wrapperCaseOrigin, wrapperCaseNew));
+ bytePrint(wrapperCaseJdk17);
+ bytePrint(wrapperCaseOrigin);
+ Assert.assertTrue(bytesEquals(wrapperCaseJdk17, wrapperCaseNew));
+ }
+ }
+
+ protected boolean bytesEquals(byte[] src, byte[] target) {
+ if (src == null && target == null) {
+ return true;
+ }
+
+ if (src == null || target == null) {
+ return false;
+ }
+
+ if (src.length != target.length) {
+ return false;
+ }
+
+ for (int i = 0; i < src.length; i++) {
+ if (src[i] != target[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void bytePrint(byte[] bytes) {
+ for (int i = 0; i < bytes.length; i++) {
+ System.out.print(bytes[i] + " ");
+ }
+ System.out.println();
+ }
+
+ private byte[] serializeEnumConstantNotPresentExceptionWrapper(SerializerFactory sf) throws IOException {
+ ExceptionWrapper exceptionWrapper = new ExceptionWrapper();
+
+ exceptionWrapper.setT(t);
+
+ os.reset();
+ Hessian2Output wrapCaseOutput = new Hessian2Output(os);
+ wrapCaseOutput.setSerializerFactory(sf);
+ wrapCaseOutput.writeObject(exceptionWrapper);
+ wrapCaseOutput.flush();
+ byte[] wrappedBytes = os.toByteArray();
+ return wrappedBytes;
+ }
+
+ private byte[] serializeEnumConstantNotPresentException(SerializerFactory sf) throws IOException {
+ os.reset();
+ Hessian2Output wrapCaseOutput = new Hessian2Output(os);
+ wrapCaseOutput.setSerializerFactory(sf);
+ wrapCaseOutput.writeObject(t);
+ wrapCaseOutput.flush();
+ byte[] wrappedBytes = os.toByteArray();
+ return wrappedBytes;
+ }
+
+}
diff --git a/src/test/java/com/caucho/hessian/io/throwable/SerializeCompatibleTest.java b/src/test/java/com/caucho/hessian/io/throwable/SerializeCompatibleTest.java
new file mode 100644
index 0000000..ffb8ac2
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/throwable/SerializeCompatibleTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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 com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.Hessian2Input;
+import com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.io.SerializerFactory;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * jdk17的兼容测试
+ * 由于throwable类必定有损
+ * 这里的测试方法为分别使用不同的方式进行序列化和反序列化, 排除允许有损的字段后, 检查其余字段
+ *
+ * @author junyuan
+ * @version SerializeCompatibleTest.java, v 0.1 2023年05月06日 15:52 junyuan Exp $
+ */
+public class SerializeCompatibleTest {
+ @BeforeClass
+ public static void setUp() {
+ factory = new SerializerFactory();
+ originFactory = new SerializeFactoryWithoutThrowable();
+ // 可以在jdk8环境下使用到专门为jdk17准备的 serializer
+ useJdk17Factory = new JDK17SerializeFactory();
+
+ os = new ByteArrayOutputStream();
+ }
+
+ /**
+ * Wrapper
+ * use jdk8 to serialize and jdk17 to deserialize
+ * 'cause' is bound to lost here as getCause may return null
+ */
+ @Test
+ public void test_case_1() throws IOException {
+ if (isLessThanJdk17) {
+ test_EnumConstantNotPresentExceptionWrapper(originFactory, useJdk17Factory);
+ }
+ }
+
+ /**
+ * Exception class directly
+ * use jdk17 to serialize and jdk8 to deserialize
+ */
+ @Test
+ public void test_case_2() throws IOException {
+ if (isLessThanJdk17) {
+ test_EnumConstantNotPresentException(originFactory, useJdk17Factory);
+ }
+ }
+
+ /**
+ * Wrapper
+ * use jdk17 to serialize and jdk8 to deserialize
+ */
+ @Test
+ public void test_case_3() throws IOException {
+ if (isLessThanJdk17) {
+ test_EnumConstantNotPresentExceptionWrapper(useJdk17Factory, originFactory);
+ }
+ }
+
+ /**
+ * Exception class directly
+ * use jdk17 to serialize and jdk8 to deserialize
+ */
+ @Test
+ public void test_case_4() throws IOException {
+ if (isLessThanJdk17) {
+ test_EnumConstantNotPresentException(useJdk17Factory, originFactory);
+ }
+ }
+
+ /**
+ * Wrapper
+ * use jdk17 to serialize and deserialize
+ */
+ @Test
+ public void test_case_5() throws IOException {
+ test_EnumConstantNotPresentExceptionWrapper(factory, factory);
+ }
+
+ /**
+ * Exception class directly
+ * use jdk17 to serialize and deserialize
+ */
+ @Test
+ public void test_case_6() throws IOException {
+ test_EnumConstantNotPresentException(factory, factory);
+ }
+
+ private static SerializerFactory useJdk17Factory;
+ private static SerializerFactory originFactory;
+ /**
+ * 和originFactory的差别有一个 stack trance element deserializer不同
+ * factory直接使用了新版的, originFactory创建了一个老版本的deserializer
+ * factory 可以在jdk8或者17环境下运行, 自适应
+ */
+ private static SerializerFactory factory;
+ private static ByteArrayOutputStream os;
+
+ private Method addSuppressed = null;
+ private Method getSuppressed = null;
+ {
+ try {
+ addSuppressed = Throwable.class.getMethod("addSuppressed", Throwable.class);
+ getSuppressed = Throwable.class.getMethod("getSuppressed");
+ } catch (Exception e) {
+
+ }
+ }
+
+ Throwable t = null;
+ {
+ Throwable x = null;
+ try {
+ t.getStackTrace();
+ } catch (NullPointerException e) {
+ x = e;
+ }
+
+ t = new EnumConstantNotPresentException(MeaninglessEnum.class, "CIG");
+
+ try {
+ addSuppressed.invoke(t, x);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static final boolean isLessThanJdk17 = isLessThanJdk17();
+
+ private static boolean isLessThanJdk17() {
+ String javaVersion = System.getProperty("java.specification.version");
+ return Double.parseDouble(javaVersion) < 17;
+ }
+
+ protected Object doEncodeNDecode(Object origin, SerializerFactory serializerFactory,
+ SerializerFactory deserializerFactory) throws IOException {
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+
+ output.setSerializerFactory(serializerFactory);
+ output.writeObject(origin);
+ output.flush();
+
+ ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+ Hessian2Input input = new Hessian2Input(is);
+ input.setSerializerFactory(deserializerFactory);
+ Object actual = input.readObject();
+ return actual;
+ }
+
+ /**
+ * EnumConstantNotPresentException 作为成员变量
+ * @throws IOException
+ */
+ private void test_EnumConstantNotPresentExceptionWrapper(SerializerFactory serialize, SerializerFactory deserialize)
+ throws IOException {
+ if (isLessThanJdk17) {
+ // jdk 17 开始不需要执行这个, 反射受限, 执行会失败
+
+ ExceptionWrapper exceptionWrapper = new ExceptionWrapper();
+ exceptionWrapper.setT(t);
+ if (addSuppressed != null) {
+ addSuppress(exceptionWrapper.getT());
+ addSuppress(exceptionWrapper.getT());
+ }
+
+ Object result = doEncodeNDecode(exceptionWrapper, serialize, deserialize);
+ Assert.assertTrue(result instanceof ExceptionWrapper);
+
+ Throwable origin = exceptionWrapper.getT();
+ Throwable target = ((ExceptionWrapper) result).getT();
+
+ // stack trace
+ Assert.assertEquals(origin.getStackTrace().length, target.getStackTrace().length);
+ Assert.assertArrayEquals(origin.getStackTrace(), target.getStackTrace());
+
+ // detail message
+ Assert.assertEquals(origin.getMessage(), target.getMessage());
+
+ // cause, now only assert on cause.detailMessage
+ if (origin.getCause() == null) {
+ // getCause为空可能是 cause == this 的情况, 此时只需要保证target也为null即可
+ // 实际该字段可能在序列化过程中有损
+ Assert.assertNull(target.getCause());
+ }
+
+ // suppress
+ Throwable[] originSuppressed = getSuppress(origin);
+ Throwable[] targetSuppressed = getSuppress(target);
+ if (originSuppressed == null && targetSuppressed == null) {
+ return;
+ }
+
+ Assert.assertTrue("one suppress is null while another is not",
+ !(originSuppressed == null || targetSuppressed == null));
+
+ Assert.assertTrue(originSuppressed.length == targetSuppressed.length);
+ for (int i = 0; i < originSuppressed.length; i++) {
+ Assert.assertEquals(originSuppressed[i].getMessage(), targetSuppressed[i].getMessage());
+ }
+ }
+ }
+
+ /**
+ * EnumConstantNotPresentException 直接进行序列化
+ * @throws IOException
+ */
+ private void test_EnumConstantNotPresentException(SerializerFactory serialize, SerializerFactory deserialize)
+ throws IOException {
+ if (isLessThanJdk17) {
+ // jdk 17 开始不需要执行这个, 反射受限, 执行会失败
+
+ if (addSuppressed != null) {
+ addSuppress(t);
+ addSuppress(t);
+ }
+
+ Object result = doEncodeNDecode(t, serialize, deserialize);
+ Assert.assertTrue(result instanceof Throwable);
+
+ Throwable origin = t;
+ Throwable target = (Throwable) result;
+
+ // stack trace
+ Assert.assertEquals(origin.getStackTrace().length, target.getStackTrace().length);
+ Assert.assertArrayEquals(origin.getStackTrace(), target.getStackTrace());
+
+ // detail message
+ Assert.assertEquals(origin.getMessage(), target.getMessage());
+
+ // cause, now only assert on cause.detailMessage
+ if (origin.getCause() == null) {
+ // getCause为空可能是 cause == this 的情况, 此时只需要保证target也为null即可
+ // 实际该字段可能在序列化过程中有损
+ Assert.assertNull(target.getCause());
+ }
+
+ // suppress
+ Throwable[] originSuppressed = getSuppress(origin);
+ Throwable[] targetSuppressed = getSuppress(target);
+ if (originSuppressed == null && targetSuppressed == null) {
+ return;
+ }
+
+ Assert.assertTrue("one suppress is null while another is not",
+ !(originSuppressed == null || targetSuppressed == null));
+
+ Assert.assertTrue(originSuppressed.length == targetSuppressed.length);
+ for (int i = 0; i < originSuppressed.length; i++) {
+ Assert.assertEquals(originSuppressed[i].getMessage(), targetSuppressed[i].getMessage());
+ }
+ }
+ }
+
+ private void addSuppress(Throwable t) {
+ Throwable suppressT1 = null;
+ try {
+ String x = null;
+ x.equals("");
+ } catch (NullPointerException e) {
+ suppressT1 = e;
+ }
+
+ try {
+ addSuppressed.invoke(t, suppressT1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private Throwable[] getSuppress(Throwable t) {
+ Throwable[] ts = null;
+ try {
+ ts = (Throwable[]) getSuppressed.invoke(t);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return ts;
+ }
+}
diff --git a/src/test/java/com/caucho/hessian/io/throwable/SerializeFactoryWithoutThrowable.java b/src/test/java/com/caucho/hessian/io/throwable/SerializeFactoryWithoutThrowable.java
new file mode 100644
index 0000000..e4525fe
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/throwable/SerializeFactoryWithoutThrowable.java
@@ -0,0 +1,92 @@
+/*
+ * 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 com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.AbstractHessianOutput;
+import com.caucho.hessian.io.Deserializer;
+import com.caucho.hessian.io.HessianProtocolException;
+import com.caucho.hessian.io.JavaDeserializer;
+import com.caucho.hessian.io.JavaSerializer;
+import com.caucho.hessian.io.Serializer;
+import com.caucho.hessian.io.SerializerFactory;
+
+import java.io.IOException;
+
+/**
+ *
+ * @author junyuan
+ * @version SerializeFactoryWithoutThrowable.java, v 0.1 2023年04月27日 19:14 junyuan Exp $
+ */
+public class SerializeFactoryWithoutThrowable extends SerializerFactory {
+
+ @Override
+ public Serializer getSerializer(Class cl) throws HessianProtocolException {
+ Serializer serializer = super.getSerializer(cl);
+
+ if (Throwable.class.isAssignableFrom(cl))
+ serializer = new OriginThrowableSerializer(cl);
+
+ if (StackTraceElement.class.isAssignableFrom(cl))
+ serializer = new JavaSerializer(cl);
+
+ _cachedSerializerMap.put(cl, serializer);
+
+ return serializer;
+ }
+
+ @Override
+ public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
+ Deserializer deserializer = super.getDeserializer(cl);
+
+ if (Throwable.class.isAssignableFrom(cl))
+ deserializer = new JavaDeserializer(cl);
+
+ if (StackTraceElement.class.isAssignableFrom(cl))
+ deserializer = new OriginStackTraceElementDeserializer();
+
+ _cachedDeserializerMap.put(cl, deserializer);
+
+ return deserializer;
+ }
+
+ private static class OriginThrowableSerializer extends JavaSerializer {
+
+ public OriginThrowableSerializer(Class cl) {
+ super(cl);
+ }
+
+ @Override
+ public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
+ Throwable t = (Throwable) obj;
+ t.getStackTrace();
+
+ super.writeObject(obj, out);
+ }
+ }
+
+ private class OriginStackTraceElementDeserializer extends JavaDeserializer {
+ public OriginStackTraceElementDeserializer() {
+ super(StackTraceElement.class);
+ }
+
+ @Override
+ protected Object instantiate() throws Exception {
+ return new StackTraceElement("", "", "", 0);
+ }
+ }
+
+}
diff --git a/src/test/java/com/caucho/hessian/io/throwable/ThrowableClassTest.java b/src/test/java/com/caucho/hessian/io/throwable/ThrowableClassTest.java
new file mode 100644
index 0000000..835e714
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/throwable/ThrowableClassTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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 com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.Hessian2Input;
+import com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.io.SerializerFactory;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ *
+ * @author junyuan
+ * @version ThrowableClassTest.java, v 0.1 2023年04月10日 14:42 junyuan Exp $
+ */
+public class ThrowableClassTest {
+
+ private static SerializerFactory factory;
+ private static ByteArrayOutputStream os;
+ private Method addSuppressed = null;
+ private Method getSuppressed = null;
+ {
+ try {
+ addSuppressed = Throwable.class.getMethod("addSuppressed", Throwable.class);
+ getSuppressed = Throwable.class.getMethod("getSuppressed");
+ } catch (Exception e) {
+
+ }
+ }
+
+ @BeforeClass
+ public static void setUp() {
+ factory = new SerializerFactory();
+ os = new ByteArrayOutputStream();
+ }
+
+ @Test
+ public void test_Throwable() throws IOException {
+ Throwable t = null;
+ try {
+ int x = 1 / 0;
+ } catch (Exception e) {
+ t = new Throwable(e);
+ }
+
+ ExceptionWrapper w = new ExceptionWrapper();
+ w.setT(t);
+ if (addSuppressed != null) {
+ addSuppress(w);
+ addSuppress(w);
+ }
+
+ Object result = doEncodeNDecode(w);
+
+ Assert.assertTrue(result instanceof ExceptionWrapper);
+ Throwable origin = w.getT();
+ Throwable target = ((ExceptionWrapper) result).getT();
+
+ // stack trace
+ Assert.assertEquals(origin.getStackTrace().length, target.getStackTrace().length);
+ Assert.assertArrayEquals(origin.getStackTrace(), target.getStackTrace());
+
+ // detail message
+ Assert.assertEquals(origin.getMessage(), target.getMessage());
+
+ // cause, now only assert on cause.detailMessage
+ Assert.assertEquals(origin.getCause().getMessage(), target.getCause().getMessage());
+
+ // suppress
+ Throwable[] originSuppressed = getSuppress(origin);
+ Throwable[] targetSuppressed = getSuppress(target);
+ if (originSuppressed == null && targetSuppressed == null) {
+ return;
+ }
+
+ Assert.assertTrue("one suppress is null while another is not",
+ !(originSuppressed == null || targetSuppressed == null));
+
+ Assert.assertTrue(originSuppressed.length == targetSuppressed.length);
+ for (int i = 0; i < originSuppressed.length; i++) {
+ Assert.assertEquals(originSuppressed[i].getMessage(), targetSuppressed[i].getMessage());
+ }
+ }
+
+ protected Object doEncodeNDecode(Object origin) throws IOException {
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+
+ output.setSerializerFactory(factory);
+ output.writeObject(origin);
+ output.flush();
+
+ ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+ Hessian2Input input = new Hessian2Input(is);
+ input.setSerializerFactory(factory);
+ Object actual = input.readObject();
+ return actual;
+ }
+
+ private void mockCause(ExceptionWrapper w) {
+ }
+
+ private void addSuppress(ExceptionWrapper w) {
+ Throwable suppressT1 = null;
+ try {
+ String x = null;
+ x.equals("");
+ } catch (NullPointerException e) {
+ suppressT1 = e;
+ }
+
+ try {
+ addSuppressed.invoke(w.getT(), suppressT1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private Throwable[] getSuppress(Throwable t) {
+ Throwable[] ts = null;
+ try {
+ ts = (Throwable[]) getSuppressed.invoke(t);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return ts;
+ }
+
+}
diff --git a/src/test/java/com/caucho/hessian/io/throwable/ThrowableJdk17SerializeTest.java b/src/test/java/com/caucho/hessian/io/throwable/ThrowableJdk17SerializeTest.java
new file mode 100644
index 0000000..0011c56
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/io/throwable/ThrowableJdk17SerializeTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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 com.caucho.hessian.io.throwable;
+
+import com.caucho.hessian.io.Hessian2Input;
+import com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.io.SerializerFactory;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * can run under JDK 6 - 17
+ * under jdk 8, reflection are used to do the serialize work
+ * under jdk17, getter and reflection take effects simultaneously
+ *
+ * @author junyuan
+ * @version ThrowableJdk17SerializeTest.java, v 0.1 2023年05月06日 17:53 junyuan Exp $
+ */
+public class ThrowableJdk17SerializeTest {
+
+ private static SerializerFactory factory;
+ private static ByteArrayOutputStream os;
+
+ private Method addSuppressed = null;
+ private Method getSuppressed = null;
+ {
+ try {
+ addSuppressed = Throwable.class.getMethod("addSuppressed", Throwable.class);
+ getSuppressed = Throwable.class.getMethod("getSuppressed");
+ } catch (Exception e) {
+
+ }
+ }
+
+ Throwable t = null;
+
+ @BeforeClass
+ public static void setUp() {
+ factory = new SerializerFactory();
+ os = new ByteArrayOutputStream();
+ }
+
+ @Test
+ public void test_EnumConstantNotPresentException() throws IOException {
+ EnumConstantNotPresentException e = new EnumConstantNotPresentException(MeaninglessEnum.class, "CIG");
+
+ try {
+ addSuppressed.invoke(e, new NullPointerException());
+ } catch (Exception e1) {
+
+ }
+
+ Object result = doEncodeNDecode(e);
+ Assert.assertTrue(result instanceof EnumConstantNotPresentException);
+
+ Throwable origin = e;
+ Throwable target = (Throwable) result;
+
+ serializeCheck(origin, target);
+ }
+
+ @Test
+ public void test_NPE() throws IOException {
+ Throwable t = null;
+ try {
+ t.getMessage();
+ } catch (NullPointerException e) {
+ t = e;
+ }
+
+ Object result = doEncodeNDecode(t);
+ Assert.assertTrue(result instanceof NullPointerException);
+
+ Throwable origin = t;
+ Throwable target = (Throwable) result;
+
+ serializeCheck(origin, target);
+ }
+
+ @Test
+ public void test_SelfDefined() throws IOException {
+ SelfDefinedException e = new SelfDefinedException("0023", "error msg");
+
+ Object result = doEncodeNDecode(e);
+ Assert.assertTrue(result instanceof SelfDefinedException);
+
+ Throwable origin = t;
+ Throwable target = (Throwable) result;
+ }
+
+ protected Object doEncodeNDecode(Object origin) throws IOException {
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+
+ output.setSerializerFactory(factory);
+ output.writeObject(origin);
+ output.flush();
+
+ ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+ Hessian2Input input = new Hessian2Input(is);
+ input.setSerializerFactory(factory);
+ Object actual = input.readObject();
+ return actual;
+ }
+
+ protected void serializeCheck(Throwable origin, Throwable target) {
+ // stack trace
+ Assert.assertEquals(origin.getStackTrace().length, target.getStackTrace().length);
+ Assert.assertArrayEquals(origin.getStackTrace(), target.getStackTrace());
+
+ // detail message
+ Assert.assertEquals(origin.getMessage(), target.getMessage());
+
+ // cause, now only assert on cause.detailMessage
+ if (origin.getCause() == null) {
+ // getCause为空可能是 cause == this 的情况, 此时只需要保证target也为null即可
+ // 实际该字段可能在序列化过程中有损
+ Assert.assertNull(target.getCause());
+ }
+
+ // suppress
+ try {
+ Throwable[] originSuppressed = (Throwable[]) getSuppressed.invoke(origin);
+ Throwable[] targetSuppressed = (Throwable[]) getSuppressed.invoke(target);
+ if (originSuppressed == null && targetSuppressed == null) {
+ return;
+ }
+ Assert.assertFalse("one suppress is null while another is not",
+ originSuppressed == null || targetSuppressed == null);
+
+ Assert.assertEquals(originSuppressed.length, targetSuppressed.length);
+ for (int i = 0; i < originSuppressed.length; i++) {
+ Assert.assertEquals(originSuppressed[i].getMessage(), targetSuppressed[i].getMessage());
+ }
+ } catch (Exception e2) {
+
+ }
+ }
+
+ private static final boolean isLessThanJdk17 = isLessThanJdk17();
+
+ private static boolean isLessThanJdk17() {
+ String javaVersion = System.getProperty("java.specification.version");
+ return Double.parseDouble(javaVersion) < 17;
+ }
+
+ {
+ Throwable x = null;
+ try {
+ t.getStackTrace();
+ } catch (NullPointerException e) {
+ x = e;
+ }
+
+ t = new EnumConstantNotPresentException(MeaninglessEnum.class, "CIG");
+
+ try {
+ addSuppressed.invoke(t, x);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/test/java/com/caucho/hessian/test/atomic/AtomicDeserializeTest.java b/src/test/java/com/caucho/hessian/test/atomic/AtomicDeserializeTest.java
new file mode 100644
index 0000000..1b73788
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/test/atomic/AtomicDeserializeTest.java
@@ -0,0 +1,274 @@
+/*
+ * 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 com.caucho.hessian.test.atomic;
+
+import com.caucho.hessian.io.Hessian2Input;
+import com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.io.SerializerFactory;
+import junit.framework.TestCase;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ *
+ * @author junyuan
+ * @version AtomicDeserializeTest.java, v 0.1 2023年03月29日 18:16 junyuan Exp $
+ */
+public class AtomicDeserializeTest {
+ private static SerializerFactory factory;
+ private static ByteArrayOutputStream os;
+
+ @BeforeClass
+ public static void setUp() {
+ factory = new SerializerFactory();
+
+ os = new ByteArrayOutputStream();
+ }
+
+ @Test
+ public void test_atomicWrapper() throws IOException {
+ os.reset();
+
+ // prepare
+ AtomicWrapper atomicWrapper = new AtomicWrapper();
+ atomicWrapper.setaInteger(new AtomicInteger(17));
+ atomicWrapper.setaBoolean(new AtomicBoolean(true));
+ atomicWrapper.setaLong(new AtomicLong(2147483649L));
+ atomicWrapper.setaReference(new AtomicReference(new Integer(1)));
+
+ atomicWrapper.setaIntegerArray(new AtomicIntegerArray(2));
+ atomicWrapper.getaIntegerArray().set(0, 0);
+ atomicWrapper.getaIntegerArray().set(1, 1);
+
+ atomicWrapper.setaLongArray(new AtomicLongArray(2));
+ atomicWrapper.getaLongArray().set(0, 0L);
+ atomicWrapper.getaLongArray().set(1, 1L);
+
+ atomicWrapper.setaReferenceArray(new AtomicReferenceArray(2));
+ atomicWrapper.getaReferenceArray().set(0, new Integer(1));
+ atomicWrapper.getaReferenceArray().set(1, new Integer(2));
+
+ // work
+ Object actual = doEncodeNDecode(atomicWrapper);
+
+ // check
+ Assert.assertTrue(actual instanceof AtomicWrapper);
+ Assert.assertEquals(atomicWrapper.getaInteger().get(), ((AtomicWrapper) actual).getaInteger().get());
+ Assert.assertEquals(atomicWrapper.getaBoolean().get(), ((AtomicWrapper) actual).getaBoolean().get());
+ Assert.assertEquals(atomicWrapper.getaLong().get(), ((AtomicWrapper) actual).getaLong().get());
+ Assert.assertEquals(atomicWrapper.getaReference().get(), ((AtomicWrapper) actual).getaReference().get());
+
+ Assert
+ .assertEquals(atomicWrapper.getaIntegerArray().get(0), ((AtomicWrapper) actual).getaIntegerArray().get(0));
+ Assert
+ .assertEquals(atomicWrapper.getaIntegerArray().get(1), ((AtomicWrapper) actual).getaIntegerArray().get(1));
+
+ Assert.assertEquals(atomicWrapper.getaLongArray().get(0), ((AtomicWrapper) actual).getaLongArray().get(0));
+ Assert.assertEquals(atomicWrapper.getaLongArray().get(1), ((AtomicWrapper) actual).getaLongArray().get(1));
+
+ Assert.assertEquals(atomicWrapper.getaReferenceArray().get(0), ((AtomicWrapper) actual).getaReferenceArray()
+ .get(0));
+ Assert.assertEquals(atomicWrapper.getaReferenceArray().get(1), ((AtomicWrapper) actual).getaReferenceArray()
+ .get(1));
+ }
+
+ @Test
+ public void test_ref() throws IOException {
+ List l = new ArrayList();
+
+ // prepare
+ AtomicInteger ai = new AtomicInteger(17);
+ AtomicBoolean ab = new AtomicBoolean(true);
+ AtomicLong al = new AtomicLong(2147483649L);
+ AtomicReference at = new AtomicReference(new Integer(1));
+ AtomicIntegerArray aia = new AtomicIntegerArray(2);
+ aia.set(0, 0);
+ aia.set(1, 1);
+ AtomicLongArray ala = new AtomicLongArray(2);
+ ala.set(0, 0L);
+ ala.set(1, 1L);
+ AtomicReferenceArray ara = new AtomicReferenceArray(2);
+ ara.set(0, new Integer(1));
+ ara.set(1, new Integer(2));
+
+ AtomicWrapper atomicWrapper1 = new AtomicWrapper();
+ atomicWrapper1.setaInteger(ai);
+ atomicWrapper1.setaBoolean(ab);
+ atomicWrapper1.setaLong(al);
+ atomicWrapper1.setaReference(at);
+ atomicWrapper1.setaIntegerArray(aia);
+ atomicWrapper1.setaLongArray(ala);
+ atomicWrapper1.setaReferenceArray(ara);
+
+ AtomicWrapper atomicWrapper2 = new AtomicWrapper();
+ atomicWrapper2.setaInteger(ai);
+ atomicWrapper2.setaBoolean(ab);
+ atomicWrapper2.setaLong(al);
+ atomicWrapper2.setaReference(at);
+ atomicWrapper2.setaIntegerArray(aia);
+ atomicWrapper2.setaLongArray(ala);
+ atomicWrapper2.setaReferenceArray(ara);
+ l.add(atomicWrapper1);
+ l.add(atomicWrapper2);
+
+ Object result = doEncodeNDecode(l);
+ Assert.assertTrue(result instanceof List);
+ List resultInstance = (List) result;
+
+ Assert.assertTrue(atomicWrapper1 instanceof AtomicWrapper);
+ AtomicWrapper actual = atomicWrapper1;
+ Assert.assertEquals(resultInstance.get(0).getaInteger().get(), actual.getaInteger().get());
+ Assert.assertEquals(resultInstance.get(0).getaBoolean().get(), actual.getaBoolean().get());
+ Assert.assertEquals(resultInstance.get(0).getaLong().get(), actual.getaLong().get());
+ Assert.assertEquals(resultInstance.get(0).getaReference().get(), actual.getaReference().get());
+
+ Assert.assertEquals(resultInstance.get(0).getaIntegerArray().get(0), actual.getaIntegerArray().get(0));
+ Assert.assertEquals(resultInstance.get(0).getaIntegerArray().get(1), actual.getaIntegerArray().get(1));
+ Assert.assertEquals(resultInstance.get(0).getaLongArray().get(0), actual.getaLongArray().get(0));
+ Assert.assertEquals(resultInstance.get(0).getaLongArray().get(1), actual.getaLongArray().get(1));
+ Assert.assertEquals(resultInstance.get(0).getaReferenceArray().get(0), actual.getaReferenceArray()
+ .get(0));
+ Assert.assertEquals(resultInstance.get(0).getaReferenceArray().get(1), actual.getaReferenceArray()
+ .get(1));
+
+ Assert.assertEquals(resultInstance.get(1).getaInteger().get(), actual.getaInteger().get());
+ Assert.assertEquals(resultInstance.get(1).getaBoolean().get(), actual.getaBoolean().get());
+ Assert.assertEquals(resultInstance.get(1).getaLong().get(), actual.getaLong().get());
+ Assert.assertEquals(resultInstance.get(1).getaReference().get(), actual.getaReference().get());
+
+ Assert.assertEquals(resultInstance.get(1).getaIntegerArray().get(0), actual.getaIntegerArray().get(0));
+ Assert.assertEquals(resultInstance.get(1).getaIntegerArray().get(1), actual.getaIntegerArray().get(1));
+ Assert.assertEquals(resultInstance.get(1).getaLongArray().get(0), actual.getaLongArray().get(0));
+ Assert.assertEquals(resultInstance.get(1).getaLongArray().get(1), actual.getaLongArray().get(1));
+ Assert.assertEquals(resultInstance.get(1).getaReferenceArray().get(0), actual.getaReferenceArray()
+ .get(0));
+ Assert.assertEquals(resultInstance.get(1).getaReferenceArray().get(1), actual.getaReferenceArray()
+ .get(1));
+
+ }
+
+ @Test
+ public void test_atomic_unwrappedInteger() throws IOException {
+ os.reset();
+ AtomicInteger i = new AtomicInteger(1);
+ Object actual = doEncodeNDecode(i);
+
+ Assert.assertTrue(actual instanceof AtomicInteger);
+ TestCase.assertEquals(i.get(), ((AtomicInteger) actual).get());
+ }
+
+ @Test
+ public void test_atomic_unwrappedLong() throws IOException {
+ os.reset();
+ AtomicLong i = new AtomicLong(1L);
+ Object actual = doEncodeNDecode(i);
+
+ Assert.assertTrue(actual instanceof AtomicLong);
+ TestCase.assertEquals(i.get(), ((AtomicLong) actual).get());
+ }
+
+ @Test
+ public void test_atomic_unwrappedBoolean() throws IOException {
+ os.reset();
+ AtomicBoolean i = new AtomicBoolean(true);
+ Object actual = doEncodeNDecode(i);
+
+ Assert.assertTrue(actual instanceof AtomicBoolean);
+ TestCase.assertEquals(i.get(), ((AtomicBoolean) actual).get());
+ }
+
+ @Test
+ public void test_atomic_unwrappedReference() throws IOException {
+ os.reset();
+ AtomicReference i = new AtomicReference(new Integer(1));
+ Object actual = doEncodeNDecode(i);
+
+ Assert.assertTrue(actual instanceof AtomicReference);
+ TestCase.assertEquals(i.get(), ((AtomicReference) actual).get());
+ }
+
+ @Test
+ public void test_atomic_unwrappedIntegerArray() throws IOException {
+ os.reset();
+ AtomicIntegerArray i = new AtomicIntegerArray(2);
+ i.set(0, 0);
+ i.set(1, 1);
+
+ Object actual = doEncodeNDecode(i);
+
+ Assert.assertTrue(actual instanceof AtomicIntegerArray);
+ Assert.assertEquals(i.get(0), ((AtomicIntegerArray) actual).get(0));
+ Assert.assertEquals(i.get(1), ((AtomicIntegerArray) actual).get(1));
+ }
+
+ @Test
+ public void test_atomic_unwrappedLongArray() throws IOException {
+ os.reset();
+ AtomicLongArray i = new AtomicLongArray(2);
+ i.set(0, 0L);
+ i.set(1, 1L);
+
+ Object actual = doEncodeNDecode(i);
+
+ Assert.assertTrue(actual instanceof AtomicLongArray);
+ Assert.assertEquals(i.get(0), ((AtomicLongArray) actual).get(0));
+ Assert.assertEquals(i.get(1), ((AtomicLongArray) actual).get(1));
+ }
+
+ @Test
+ public void test_atomic_unwrappedReferenceArray() throws IOException {
+ os.reset();
+ AtomicReferenceArray i = new AtomicReferenceArray(2);
+ i.set(0, new Integer(0));
+ i.set(1, new Integer(1));
+
+ Object actual = doEncodeNDecode(i);
+
+ Assert.assertTrue(actual instanceof AtomicReferenceArray);
+ Assert.assertEquals(i.get(0), ((AtomicReferenceArray) actual).get(0));
+ Assert.assertEquals(i.get(1), ((AtomicReferenceArray) actual).get(1));
+ }
+
+ protected Object doEncodeNDecode(Object origin) throws IOException {
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+
+ output.setSerializerFactory(factory);
+ output.writeObject(origin);
+ output.flush();
+
+ ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+ Hessian2Input input = new Hessian2Input(is);
+ input.setSerializerFactory(factory);
+ Object actual = input.readObject();
+ return actual;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/caucho/hessian/test/atomic/AtomicWrapper.java b/src/test/java/com/caucho/hessian/test/atomic/AtomicWrapper.java
new file mode 100644
index 0000000..bbc08c5
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/test/atomic/AtomicWrapper.java
@@ -0,0 +1,167 @@
+/*
+ * 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 com.caucho.hessian.test.atomic;
+
+import java.io.Serializable;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ *
+ * @author junyuan
+ * @version AtomicWrapper.java, v 0.1 2023年03月29日 18:15 junyuan Exp $
+ */
+public class AtomicWrapper implements Serializable {
+ private AtomicInteger aInteger;
+ private AtomicBoolean aBoolean;
+ private AtomicLong aLong;
+ private AtomicReference aReference;
+ private AtomicIntegerArray aIntegerArray;
+ private AtomicLongArray aLongArray;
+ private AtomicReferenceArray aReferenceArray;
+
+ /**
+ * Getter method for property aInteger.
+ *
+ * @return property value of aInteger
+ */
+ public AtomicInteger getaInteger() {
+ return aInteger;
+ }
+
+ /**
+ * Setter method for property aInteger.
+ *
+ * @param aInteger value to be assigned to property aInteger
+ */
+ public void setaInteger(AtomicInteger aInteger) {
+ this.aInteger = aInteger;
+ }
+
+ /**
+ * Getter method for property aBoolean.
+ *
+ * @return property value of aBoolean
+ */
+ public AtomicBoolean getaBoolean() {
+ return aBoolean;
+ }
+
+ /**
+ * Setter method for property aBoolean.
+ *
+ * @param aBoolean value to be assigned to property aBoolean
+ */
+ public void setaBoolean(AtomicBoolean aBoolean) {
+ this.aBoolean = aBoolean;
+ }
+
+ /**
+ * Getter method for property aLong.
+ *
+ * @return property value of aLong
+ */
+ public AtomicLong getaLong() {
+ return aLong;
+ }
+
+ /**
+ * Setter method for property aLong.
+ *
+ * @param aLong value to be assigned to property aLong
+ */
+ public void setaLong(AtomicLong aLong) {
+ this.aLong = aLong;
+ }
+
+ /**
+ * Getter method for property aReference.
+ *
+ * @return property value of aReference
+ */
+ public AtomicReference getaReference() {
+ return aReference;
+ }
+
+ /**
+ * Setter method for property aReference.
+ *
+ * @param aReference value to be assigned to property aReference
+ */
+ public void setaReference(AtomicReference aReference) {
+ this.aReference = aReference;
+ }
+
+ /**
+ * Getter method for property aIntegerArray.
+ *
+ * @return property value of aIntegerArray
+ */
+ public AtomicIntegerArray getaIntegerArray() {
+ return aIntegerArray;
+ }
+
+ /**
+ * Setter method for property aIntegerArray.
+ *
+ * @param aIntegerArray value to be assigned to property aIntegerArray
+ */
+ public void setaIntegerArray(AtomicIntegerArray aIntegerArray) {
+ this.aIntegerArray = aIntegerArray;
+ }
+
+ /**
+ * Getter method for property aLongArray.
+ *
+ * @return property value of aLongArray
+ */
+ public AtomicLongArray getaLongArray() {
+ return aLongArray;
+ }
+
+ /**
+ * Setter method for property aLongArray.
+ *
+ * @param aLongArray value to be assigned to property aLongArray
+ */
+ public void setaLongArray(AtomicLongArray aLongArray) {
+ this.aLongArray = aLongArray;
+ }
+
+ /**
+ * Getter method for property aReferenceArray.
+ *
+ * @return property value of aReferenceArray
+ */
+ public AtomicReferenceArray getaReferenceArray() {
+ return aReferenceArray;
+ }
+
+ /**
+ * Setter method for property aReferenceArray.
+ *
+ * @param aReferenceArray value to be assigned to property aReferenceArray
+ */
+ public void setaReferenceArray(AtomicReferenceArray aReferenceArray) {
+ this.aReferenceArray = aReferenceArray;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/caucho/hessian/test/atomic/SerializeCompatibleTest.java b/src/test/java/com/caucho/hessian/test/atomic/SerializeCompatibleTest.java
new file mode 100644
index 0000000..192c2e6
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/test/atomic/SerializeCompatibleTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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 com.caucho.hessian.test.atomic;
+
+import com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.io.SerializerFactory;
+import com.caucho.hessian.io.atomic.AtomicSerializer;
+import com.caucho.hessian.io.throwable.JDK17SerializeFactory;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * for version 3.5.0
+ * assure that the result bytes of Atomic classes remains, whatever as object or field, same as
+ * previous version
+ *
+ * @author junyuan
+ * @version SerializeCompatibleTest.java, v 0.1 2023年03月31日 16:40 junyuan Exp $
+ */
+public class SerializeCompatibleTest {
+ private static SerializerFactory factory;
+ private static SerializerFactory useJdk17Factory;
+
+ private static ByteArrayOutputStream os;
+
+ private static final boolean isLessThanJdk17 = isLessThanJdk17();
+
+ private static boolean isLessThanJdk17() {
+ String javaVersion = System.getProperty("java.specification.version");
+ return Double.parseDouble(javaVersion) < 17;
+ }
+
+ @BeforeClass
+ public static void setUp() {
+ factory = new SerializerFactory();
+
+ os = new ByteArrayOutputStream();
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ try {
+ Field field = SerializerFactory.class.getDeclaredField("_staticSerializerMap");
+ field.setAccessible(true);
+ ConcurrentMap map = (ConcurrentMap) field.get(SerializerFactory.class);
+ AtomicSerializer atomicSerializer = new AtomicSerializer();
+ map.put(AtomicInteger.class, atomicSerializer);
+ map.put(AtomicLong.class, atomicSerializer);
+ map.put(AtomicBoolean.class, atomicSerializer);
+ map.put(AtomicReference.class, atomicSerializer);
+ map.put(AtomicLongArray.class, atomicSerializer);
+ map.put(AtomicIntegerArray.class, atomicSerializer);
+ map.put(AtomicReferenceArray.class, atomicSerializer);
+ } catch (Throwable t) {
+ }
+ }
+
+ /**
+ * result byte should be in same with former version so as to behaving compatible
+ * @throws IOException
+ */
+ @Test
+ public void test_serialize() throws IOException, NoSuchFieldException, IllegalAccessException {
+ if (!isLessThanJdk17) {
+ return;
+ }
+
+ byte[] wrappedCaseAtomic = serializeAtomicWrapper();
+ byte[] unwrappedIntegerAtomic = serializeAtomicInteger();
+ byte[] unwrappedBooleanAtomic = serializeAtomicBoolean();
+ byte[] unwrappedLongAtomic = serializeAtomicLong();
+ byte[] unwrappedReferenceAtomic = serializeAtomicReference();
+ byte[] unwrappedIntegerArrayAtomic = serializeAtomicIntegerArray();
+ byte[] unwrappedLongArrayAtomic = serializeAtomicLongArray();
+ byte[] unwrappedReferenceArrayAtomic = serializeAtomicReferenceArray();
+
+ // use origin java serialize then
+ Field field = SerializerFactory.class.getDeclaredField("_staticSerializerMap");
+ field.setAccessible(true);
+ ConcurrentMap map = (ConcurrentMap) field.get(SerializerFactory.class);
+ map.remove(AtomicInteger.class);
+ map.remove(AtomicBoolean.class);
+ map.remove(AtomicLong.class);
+ map.remove(AtomicReference.class);
+ map.remove(AtomicIntegerArray.class);
+ map.remove(AtomicLongArray.class);
+ map.remove(AtomicReferenceArray.class);
+
+ byte[] wrappedCaseJava = serializeAtomicWrapper();
+ byte[] unwrappedIntegerJava = serializeAtomicInteger();
+ byte[] unwrappedBooleanJava = serializeAtomicBoolean();
+ byte[] unwrappedLongJava = serializeAtomicLong();
+ byte[] unwrappedReferenceJava = serializeAtomicReference();
+ byte[] unwrappedIntegerArrayJava = serializeAtomicIntegerArray();
+ byte[] unwrappedLongArrayJava = serializeAtomicLongArray();
+ byte[] unwrappedReferenceArrayJava = serializeAtomicReferenceArray();
+
+ Assert.assertTrue(bytesEquals(wrappedCaseAtomic, wrappedCaseJava));
+
+ Assert.assertTrue(bytesEquals(unwrappedIntegerAtomic, unwrappedIntegerJava));
+ Assert.assertTrue(bytesEquals(unwrappedBooleanAtomic, unwrappedBooleanJava));
+ Assert.assertTrue(bytesEquals(unwrappedLongAtomic, unwrappedLongJava));
+ Assert.assertTrue(bytesEquals(unwrappedReferenceAtomic, unwrappedReferenceJava));
+ Assert.assertTrue(bytesEquals(unwrappedIntegerArrayAtomic, unwrappedIntegerArrayJava));
+ Assert.assertTrue(bytesEquals(unwrappedLongArrayAtomic, unwrappedLongArrayJava));
+ Assert.assertTrue(bytesEquals(unwrappedReferenceArrayAtomic, unwrappedReferenceArrayJava));
+ }
+
+ protected boolean bytesEquals(byte[] src, byte[] target) {
+ if (src == null && target == null) {
+ return true;
+ }
+
+ if (src == null || target == null) {
+ return false;
+ }
+
+ if (src.length != target.length) {
+ return false;
+ }
+
+ for (int i = 0; i < src.length; i++) {
+ if (src[i] != target[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private byte[] serializeAtomicWrapper() throws IOException {
+ // wrapped
+ AtomicWrapper atomicWrapper = new AtomicWrapper();
+ atomicWrapper.setaInteger(new AtomicInteger(17));
+ atomicWrapper.setaBoolean(new AtomicBoolean(true));
+ atomicWrapper.setaLong(new AtomicLong(2147483649L));
+ atomicWrapper.setaReference(new AtomicReference());
+ atomicWrapper.getaReference().set(new Integer(1));
+
+ atomicWrapper.setaIntegerArray(new AtomicIntegerArray(2));
+ atomicWrapper.getaIntegerArray().set(0, 0);
+ atomicWrapper.getaIntegerArray().set(1, 1);
+
+ atomicWrapper.setaLongArray(new AtomicLongArray(2));
+ atomicWrapper.getaLongArray().set(0, 0L);
+ atomicWrapper.getaLongArray().set(1, 1L);
+
+ atomicWrapper.setaReferenceArray(new AtomicReferenceArray(2));
+ atomicWrapper.getaReferenceArray().set(0, new Integer(1));
+ atomicWrapper.getaReferenceArray().set(1, new Integer(2));
+
+ os.reset();
+ Hessian2Output wrapCaseOutput = new Hessian2Output(os);
+
+ wrapCaseOutput.setSerializerFactory(factory);
+ wrapCaseOutput.writeObject(atomicWrapper);
+ wrapCaseOutput.flush();
+ byte[] wrappedBytes = os.toByteArray();
+ return wrappedBytes;
+ }
+
+ private byte[] serializeAtomicInteger() throws IOException {
+ AtomicInteger atomic = new AtomicInteger(1);
+
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+ output.setSerializerFactory(factory);
+ output.writeObject(atomic);
+ output.flush();
+ byte[] unwrappedBytes = os.toByteArray();
+ return unwrappedBytes;
+ }
+
+ private byte[] serializeAtomicBoolean() throws IOException {
+ AtomicBoolean atomic = new AtomicBoolean(true);
+
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+ output.setSerializerFactory(factory);
+ output.writeObject(atomic);
+ output.flush();
+ byte[] unwrappedBytes = os.toByteArray();
+ return unwrappedBytes;
+ }
+
+ private byte[] serializeAtomicLong() throws IOException {
+ AtomicLong atomic = new AtomicLong(1L);
+
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+ output.setSerializerFactory(factory);
+ output.writeObject(atomic);
+ output.flush();
+ byte[] unwrappedBytes = os.toByteArray();
+ return unwrappedBytes;
+ }
+
+ private byte[] serializeAtomicReference() throws IOException {
+ AtomicReference atomic = new AtomicReference(new Integer(1));
+
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+ output.setSerializerFactory(factory);
+ output.writeObject(atomic);
+ output.flush();
+ byte[] unwrappedBytes = os.toByteArray();
+ return unwrappedBytes;
+ }
+
+ private byte[] serializeAtomicIntegerArray() throws IOException {
+ AtomicIntegerArray array = new AtomicIntegerArray(2);
+ array.set(0, 0);
+ array.set(1, 1);
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+ output.setSerializerFactory(factory);
+ output.writeObject(array);
+ output.flush();
+ byte[] unwrappedBytes = os.toByteArray();
+ return unwrappedBytes;
+ }
+
+ private byte[] serializeAtomicLongArray() throws IOException {
+ AtomicLongArray array = new AtomicLongArray(2);
+ array.set(0, 0L);
+ array.set(1, 1L);
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+ output.setSerializerFactory(factory);
+ output.writeObject(array);
+ output.flush();
+ byte[] unwrappedBytes = os.toByteArray();
+ return unwrappedBytes;
+ }
+
+ private byte[] serializeAtomicReferenceArray() throws IOException {
+ AtomicReferenceArray array = new AtomicReferenceArray(2);
+ array.set(0, new Integer(0));
+ array.set(1, new Integer(1));
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+ output.setSerializerFactory(factory);
+ output.writeObject(array);
+ output.flush();
+ byte[] unwrappedBytes = os.toByteArray();
+ return unwrappedBytes;
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/caucho/hessian/test/stacktrace/ExceptionClassTest.java b/src/test/java/com/caucho/hessian/test/stacktrace/ExceptionClassTest.java
new file mode 100644
index 0000000..30db446
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/test/stacktrace/ExceptionClassTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.caucho.hessian.test.stacktrace;
+
+import com.caucho.hessian.io.Hessian2Input;
+import com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.io.SerializerFactory;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author junyuan
+ * @version ExceptionClassTest.java, v 0.1 2023年04月04日 15:38 junyuan Exp $
+ */
+public class ExceptionClassTest {
+
+ private static SerializerFactory factory;
+ private static ByteArrayOutputStream os;
+
+ @BeforeClass
+ public static void setUp() {
+ factory = new SerializerFactory();
+ os = new ByteArrayOutputStream();
+ }
+
+ @Test
+ public void test_stacktrace() throws IOException {
+ Throwable t = null;
+ try {
+ int x = 1 / 0;
+ } catch (Exception e) {
+ t = e;
+ }
+
+ ExceptionWrapper w = new ExceptionWrapper();
+ w.setT(t);
+
+ Object result = doEncodeNDecode(w);
+
+ }
+
+ @Test
+ public void test_ref() throws IOException {
+ Throwable t = null;
+ try {
+ int x = 1 / 0;
+ } catch (Exception e) {
+ t = e;
+ }
+
+ ExceptionWrapper w1 = new ExceptionWrapper();
+ w1.setT(t);
+
+ ExceptionWrapper w2 = new ExceptionWrapper();
+ w2.setT(t);
+
+ List l = new ArrayList();
+ l.add(w1);
+ l.add(w2);
+
+ Object result = doEncodeNDecode(l);
+ Assert.assertTrue(result instanceof List);
+
+ List resultInstance = (List) result;
+
+ Assert.assertEquals(l.get(0).t.getMessage(), resultInstance.get(0).t.getMessage());
+ Assert.assertEquals(l.get(0).t.getStackTrace().length, resultInstance.get(0).t.getStackTrace().length);
+
+ Assert.assertEquals(l.get(1).t.getMessage(), resultInstance.get(1).t.getMessage());
+ }
+
+ protected Object doEncodeNDecode(Object origin) throws IOException {
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+
+ output.setSerializerFactory(factory);
+ output.writeObject(origin);
+ output.flush();
+
+ ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+ Hessian2Input input = new Hessian2Input(is);
+ input.setSerializerFactory(factory);
+ Object actual = input.readObject();
+ return actual;
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/caucho/hessian/test/stacktrace/ExceptionWrapper.java b/src/test/java/com/caucho/hessian/test/stacktrace/ExceptionWrapper.java
new file mode 100644
index 0000000..d7a39a9
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/test/stacktrace/ExceptionWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.caucho.hessian.test.stacktrace;
+
+import java.io.Serializable;
+
+/**
+ *
+ * @author junyuan
+ * @version ExceptionWrapper.java, v 0.1 2023年04月04日 15:40 junyuan Exp $
+ */
+public class ExceptionWrapper implements Serializable {
+ private static final long serialVersionUID = 4065571790594438646L;
+
+ Throwable t;
+
+ /**
+ * Getter method for property t.
+ *
+ * @return property value of t
+ */
+ public Throwable getT() {
+ return t;
+ }
+
+ /**
+ * Setter method for property t.
+ *
+ * @param t value to be assigned to property t
+ */
+ public void setT(Throwable t) {
+ this.t = t;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/caucho/hessian/test/stacktrace/SerializeCompatibleTest.java b/src/test/java/com/caucho/hessian/test/stacktrace/SerializeCompatibleTest.java
new file mode 100644
index 0000000..d15dbb1
--- /dev/null
+++ b/src/test/java/com/caucho/hessian/test/stacktrace/SerializeCompatibleTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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 com.caucho.hessian.test.stacktrace;
+
+import com.caucho.hessian.io.Hessian2Output;
+import com.caucho.hessian.io.SerializerFactory;
+import com.caucho.hessian.io.throwable.StackTraceElementSerializer;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ *
+ * @author junyuan
+ * @version SerializeCompatibleSelfTest.java, v 0.1 2023年04月10日 14:52 junyuan Exp $
+ */
+public class SerializeCompatibleTest {
+
+ private static final boolean isLessThanJdk17 = isLessThanJdk17();
+
+ private static boolean isLessThanJdk17() {
+ String javaVersion = System.getProperty("java.specification.version");
+ return Double.parseDouble(javaVersion) < 17;
+ }
+
+ private static SerializerFactory factory;
+ private static ByteArrayOutputStream os;
+
+ Throwable t = null;
+
+ @BeforeClass
+ public static void setUp() {
+ factory = new SerializerFactory();
+
+ os = new ByteArrayOutputStream();
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ try {
+ Field field = SerializerFactory.class.getDeclaredField("_staticSerializerMap");
+ field.setAccessible(true);
+ ConcurrentMap map = (ConcurrentMap) field.get(SerializerFactory.class);
+ StackTraceElementSerializer serializer = new StackTraceElementSerializer();
+ map.put(StackTraceElement.class, serializer);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+ {
+ try {
+ double x = 1 / 0;
+ } catch (Exception e) {
+ t = e;
+ }
+ }
+
+ /**
+ * result byte should be in same with former version so as to behaving compatible
+ * @throws IOException
+ */
+ @Test
+ public void test_serialize() throws IOException, NoSuchFieldException, IllegalAccessException {
+ if (!isLessThanJdk17) {
+ return;
+ }
+ byte[] wrappedCaseStackTrace = serializeExceptionWrapper();
+ byte[] unwrappedStackTrace = serializeStackTraceElement();
+
+ // use origin java serialize then
+ Field field = SerializerFactory.class.getDeclaredField("_staticSerializerMap");
+ field.setAccessible(true);
+ ConcurrentMap map = (ConcurrentMap) field.get(SerializerFactory.class);
+ map.remove(StackTraceElement.class);
+
+ byte[] wrappedStackTraceCaseJava = serializeExceptionWrapper();
+ byte[] unwrappedStackTraceJava = serializeStackTraceElement();
+
+ Assert.assertTrue(bytesEquals(wrappedCaseStackTrace, wrappedStackTraceCaseJava));
+
+ Assert.assertTrue(bytesEquals(unwrappedStackTrace, unwrappedStackTraceJava));
+ }
+
+ protected boolean bytesEquals(byte[] src, byte[] target) {
+ if (src == null && target == null) {
+ return true;
+ }
+
+ if (src == null || target == null) {
+ return false;
+ }
+
+ if (src.length != target.length) {
+ return false;
+ }
+
+ for (int i = 0; i < src.length; i++) {
+ if (src[i] != target[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private byte[] serializeExceptionWrapper() throws IOException {
+ // wrapped
+ com.caucho.hessian.io.throwable.ExceptionWrapper exceptionWrapper = new com.caucho.hessian.io.throwable.ExceptionWrapper();
+
+ exceptionWrapper.setT(t);
+
+ os.reset();
+ Hessian2Output wrapCaseOutput = new Hessian2Output(os);
+
+ wrapCaseOutput.setSerializerFactory(factory);
+ wrapCaseOutput.writeObject(exceptionWrapper);
+ wrapCaseOutput.flush();
+ byte[] wrappedBytes = os.toByteArray();
+ return wrappedBytes;
+ }
+
+ private byte[] serializeStackTraceElement() throws IOException {
+ Throwable t = null;
+ try {
+ double x = 1 / 0;
+ } catch (Exception e) {
+ t = e;
+ }
+ StackTraceElement e = t.getStackTrace()[0];
+ ;
+
+ os.reset();
+ Hessian2Output output = new Hessian2Output(os);
+ output.setSerializerFactory(factory);
+ output.writeObject(e);
+ output.flush();
+ byte[] unwrappedBytes = os.toByteArray();
+ return unwrappedBytes;
+ }
+
+}