Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ public interface MethodDescriptor {

Class<?>[] getParameterClasses();

/**
* Retrieves the generic parameter types of the method.
* <p>
* For parameterized parameters like {@code List<Byte>} this returns the
* {@link java.lang.reflect.ParameterizedType} instead of just the raw {@code Class},
* which allows deserializers to preserve narrow element types (Byte/Short/Float)
* nested inside generic collections.
*
* @return the generic parameter types
*/
default Type[] getGenericParameterTypes() {
return getParameterClasses();
}

Class<?> getReturnClass();

Type[] getReturnTypes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class ReflectionMethodDescriptor implements MethodDescriptor {
public final String methodName;
private final String[] compatibleParamSignatures;
private final Class<?>[] parameterClasses;
private final Type[] genericParameterTypes;
private final Class<?> returnClass;
private final Type[] returnTypes;
private final String paramDesc;
Expand All @@ -57,6 +58,7 @@ public ReflectionMethodDescriptor(Method method) {
this.method = method;
this.methodName = method.getName();
this.parameterClasses = method.getParameterTypes();
this.genericParameterTypes = method.getGenericParameterTypes();
this.returnClass = method.getReturnType();
Type[] returnTypesResult;
try {
Expand Down Expand Up @@ -149,6 +151,11 @@ public Class<?>[] getParameterClasses() {
return parameterClasses;
}

@Override
public Type[] getGenericParameterTypes() {
return genericParameterTypes;
}

@Override
public String getParamDesc() {
return paramDesc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
Expand Down Expand Up @@ -82,6 +83,8 @@ public class DecodeableRpcInvocation extends RpcInvocation implements Codec, Dec

protected final transient Supplier<CallbackServiceCodec> callbackServiceCodecFactory;

private transient Type[] genericParameterTypes;

private static final boolean CHECK_SERIALIZATION =
Boolean.parseBoolean(SystemPropertyConfigUtils.getSystemProperty(SERIALIZATION_SECURITY_CHECK_KEY, "true"));

Expand Down Expand Up @@ -237,6 +240,7 @@ protected Class<?>[] drawPts(String path, String version, String desc, Class<?>[
MethodDescriptor methodDescriptor = serviceDescriptor.getMethod(getMethodName(), desc);
if (methodDescriptor != null) {
pts = methodDescriptor.getParameterClasses();
this.genericParameterTypes = methodDescriptor.getGenericParameterTypes();
this.setReturnTypes(methodDescriptor.getReturnTypes());

// switch TCCL
Expand Down Expand Up @@ -273,8 +277,14 @@ protected Class<?>[] drawPts(String path, String version, String desc, Class<?>[
protected Object[] drawArgs(ObjectInput in, Class<?>[] pts) throws IOException, ClassNotFoundException {
Object[] args;
args = new Object[pts.length];
Type[] genericTypes = this.genericParameterTypes;
for (int i = 0; i < args.length; i++) {
args[i] = in.readObject(pts[i]);
Type genericType = (genericTypes != null && i < genericTypes.length) ? genericTypes[i] : null;
if (genericType != null) {
args[i] = in.readObject(pts[i], genericType);
} else {
args[i] = in.readObject(pts[i]);
}
}
return args;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

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

Expand Down Expand Up @@ -119,14 +127,110 @@ public <T> T readObject(Class<T> cls) throws IOException, ClassNotFoundException
}

@Override
@SuppressWarnings("unchecked")
public <T> T readObject(Class<T> cls, Type type) throws IOException, ClassNotFoundException {
if (!Objects.equals(
mH2i.getSerializerFactory().getClassLoader(),
Thread.currentThread().getContextClassLoader())) {
mH2i.setSerializerFactory(hessian2FactoryManager.getSerializerFactory(
Thread.currentThread().getContextClassLoader()));
}
return readObject(cls);
if (type instanceof ParameterizedType && containsNarrowableType(type)) {
// hessian2 encodes Byte/Short/Integer all as int and Float/Double as double on the wire,
// so the element type of such generic collections can only be restored from the declared
// generic type. hessian's expectedTypes mechanism is single-level, so nested generics
// (e.g. List<List<Byte>>, Map<String, List<Byte>>) lose the narrow element type. Read the
// object with the erased type and then recursively narrow numeric elements to match the
// declared generic type.
Object obj = mH2i.readObject(cls);
return (T) narrowByType(obj, type);
}
return (T) mH2i.readObject(cls);
}

/**
* Checks whether the given {@link Type} declares any narrow primitive-wrapper element type
* (Byte/Short/Float/Character) anywhere in the generic hierarchy. Only such types need the
* recursive narrowing pass, avoiding unnecessary copies for e.g. {@code List<String>}.
*/
private boolean containsNarrowableType(Type type) {
if (type instanceof ParameterizedType) {
Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();
for (Type typeArg : typeArgs) {
if (containsNarrowableType(typeArg)) {
return true;
}
}
return false;
}
return type == Byte.class || type == Short.class || type == Float.class || type == Character.class;
}

/**
* Recursively narrows widened numeric elements (Integer/Double) inside generic collections to the
* element types declared by {@code type}. Returns the input object unchanged when no element needs
* to be narrowed.
*/
@SuppressWarnings("unchecked")
private Object narrowByType(Object obj, Type type) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
Type[] typeArgs = parameterizedType.getActualTypeArguments();
if (rawType instanceof Class
&& Collection.class.isAssignableFrom((Class<?>) rawType)
&& typeArgs.length == 1) {
Type elementType = typeArgs[0];
if (obj instanceof List) {
List<Object> result = new ArrayList<>(((List<?>) obj).size());
for (Object element : (List<?>) obj) {
result.add(narrowByType(element, elementType));
}
return result;
}
if (obj instanceof Set) {
Set<Object> result = new LinkedHashSet<>();
for (Object element : (Set<?>) obj) {
result.add(narrowByType(element, elementType));
}
return result;
}
if (obj instanceof Collection) {
Collection<Object> result = new ArrayList<>();
for (Object element : (Collection<?>) obj) {
result.add(narrowByType(element, elementType));
}
return result;
}
} else if (rawType instanceof Class
&& Map.class.isAssignableFrom((Class<?>) rawType)
&& typeArgs.length == 2) {
Type keyType = typeArgs[0];
Type valueType = typeArgs[1];
if (obj instanceof Map) {
Map<Object, Object> result = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : ((Map<?, ?>) obj).entrySet()) {
result.put(narrowByType(entry.getKey(), keyType), narrowByType(entry.getValue(), valueType));
}
return result;
}
}
} else if (type instanceof Class) {
Class<?> clazz = (Class<?>) type;
if (clazz == Byte.class && obj instanceof Integer) {
return Byte.valueOf(((Number) obj).byteValue());
}
if (clazz == Short.class && obj instanceof Integer) {
return Short.valueOf(((Number) obj).shortValue());
}
if (clazz == Float.class && obj instanceof Double) {
return Float.valueOf(((Number) obj).floatValue());
}
if (clazz == Character.class && obj instanceof Integer) {
return (char) ((Number) obj).intValue();
}
}
return obj;
}

public InputStream readInputStream() throws IOException {
Expand Down
Loading
Loading