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; + } + +}