Skip to content

Commit f03431e

Browse files
author
Jenkins Contributor
committed
Fix Jenkins Issue 26077 - Support Record types in XStream2
1 parent 396f78b commit f03431e

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

core/src/main/java/hudson/util/RobustReflectionConverter.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,101 @@ protected void marshallField(final MarshallingContext context, Object newObj, Fi
285285

286286
@Override
287287
public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
288+
String readResolveValue = reader.getAttribute(mapper.aliasForAttribute("resolves-to"));
289+
Class type = readResolveValue != null ? mapper.realClass(readResolveValue) : context.getRequiredType();
290+
if (type != null && type.isRecord()) {
291+
return unmarshalRecord(reader, context, type);
292+
}
293+
288294
Object result = instantiateNewInstance(reader, context);
289295
result = doUnmarshal(result, reader, context);
290296
return serializationMethodInvoker.callReadResolve(result);
291297
}
292298

299+
private Object unmarshalRecord(final HierarchicalStreamReader reader, final UnmarshallingContext context, Class type) {
300+
Map<String, Object> values = new HashMap<>();
301+
302+
Iterator it = reader.getAttributeNames();
303+
while (it.hasNext()) {
304+
String attrAlias = (String) it.next();
305+
String attrName = mapper.attributeForAlias(attrAlias);
306+
Field field = reflectionProvider.getFieldOrNull(type, attrName);
307+
if (field != null) {
308+
SingleValueConverter converter = mapper.getConverterFromAttribute(field.getDeclaringClass(), attrName, field.getType());
309+
Class fieldType = field.getType();
310+
if (converter == null) {
311+
converter = mapper.getConverterFromItemType(fieldType);
312+
}
313+
if (converter != null) {
314+
Object value = converter.fromString(reader.getAttribute(attrAlias));
315+
if (fieldType.isPrimitive()) {
316+
fieldType = Primitives.box(fieldType);
317+
}
318+
if (value != null && !fieldType.isAssignableFrom(value.getClass())) {
319+
throw new ConversionException("Cannot convert type " + value.getClass().getName() + " to type " + fieldType.getName());
320+
}
321+
values.put(attrName, value);
322+
}
323+
}
324+
}
325+
326+
while (reader.hasMoreChildren()) {
327+
reader.moveDown();
328+
try {
329+
String fieldName = mapper.realMember(type, reader.getNodeName());
330+
Field field = reflectionProvider.getFieldOrNull(type, fieldName);
331+
if (field != null) {
332+
Class fieldType = field.getType();
333+
Class xmlType = mapper.defaultImplementationOf(fieldType);
334+
String classAttribute = reader.getAttribute(mapper.aliasForAttribute("class"));
335+
if (classAttribute != null) {
336+
Class specifiedType = mapper.realClass(classAttribute);
337+
if (fieldType.isAssignableFrom(specifiedType)) {
338+
xmlType = specifiedType;
339+
}
340+
}
341+
Object value = unmarshalField(context, null, xmlType, field);
342+
if (value != null && !xmlType.isAssignableFrom(value.getClass())) {
343+
LOGGER.warning("Cannot convert type " + value.getClass().getName() + " to type " + xmlType.getName());
344+
} else {
345+
values.put(fieldName, value);
346+
}
347+
} else {
348+
Class itemType = mapper.getItemTypeForItemFieldName(type, fieldName);
349+
Class xmlType = itemType != null ? itemType : mapper.realClass(reader.getNodeName());
350+
context.convertAnother(null, xmlType);
351+
}
352+
} catch (CriticalXStreamException | InputManipulationException e) {
353+
throw e;
354+
} catch (XStreamException | LinkageError e) {
355+
addErrorInContext(context, e);
356+
}
357+
reader.moveUp();
358+
}
359+
360+
try {
361+
java.lang.reflect.RecordComponent[] components = type.getRecordComponents();
362+
Class[] parameterTypes = new Class[components.length];
363+
Object[] args = new Object[components.length];
364+
for (int i = 0; i < components.length; i++) {
365+
java.lang.reflect.RecordComponent component = components[i];
366+
Class pType = component.getType();
367+
String name = component.getName();
368+
parameterTypes[i] = pType;
369+
Object val = values.get(name);
370+
if (val == null && pType.isPrimitive()) {
371+
val = java.lang.reflect.Array.get(java.lang.reflect.Array.newInstance(pType, 1), 0);
372+
}
373+
args[i] = val;
374+
}
375+
java.lang.reflect.Constructor constructor = type.getDeclaredConstructor(parameterTypes);
376+
constructor.setAccessible(true);
377+
return constructor.newInstance(args);
378+
} catch (Exception e) {
379+
throw new ConversionException("Failed to instantiate record " + type.getName(), e);
380+
}
381+
}
382+
293383
public Object doUnmarshal(final Object result, final HierarchicalStreamReader reader, final UnmarshallingContext context) {
294384
final SeenFields seenFields = new SeenFields();
295385
Iterator it = reader.getAttributeNames();

0 commit comments

Comments
 (0)