public Object doUnmarshal()

in core/src/main/java/hudson/util/RobustReflectionConverter.java [273:390]


    public Object doUnmarshal(final Object result, final HierarchicalStreamReader reader, final UnmarshallingContext context) {
        final SeenFields seenFields = new SeenFields();
        Iterator it = reader.getAttributeNames();
        // Remember outermost Saveable encountered, for reporting below
        if (result instanceof Saveable && context.get("Saveable") == null)
            context.put("Saveable", result);

        // Process attributes before recursing into child elements.
        while (it.hasNext()) {
            String attrAlias = (String) it.next();
            String attrName = mapper.attributeForAlias(attrAlias);
            Class classDefiningField = determineWhichClassDefinesField(reader);
            boolean fieldExistsInClass = fieldDefinedInClass(result, attrName);
            if (fieldExistsInClass) {
                Field field = reflectionProvider.getField(result.getClass(), attrName);
                SingleValueConverter converter = mapper.getConverterFromAttribute(field.getDeclaringClass(),attrName,field.getType());
                Class type = field.getType();
                if (converter == null) {
                    converter = mapper.getConverterFromItemType(type);
                }
                if (converter != null) {
                    Object value = converter.fromString(reader.getAttribute(attrAlias));
                    if (type.isPrimitive()) {
                        type = Primitives.box(type);
                    }
                    if (value != null && !type.isAssignableFrom(value.getClass())) {
                        throw new ConversionException("Cannot convert type " + value.getClass().getName() + " to type " + type.getName());
                    }
                    reflectionProvider.writeField(result, attrName, value, classDefiningField);
                    seenFields.add(classDefiningField, attrName);
                }
            }
        }

        Map implicitCollectionsForCurrentObject = null;
        while (reader.hasMoreChildren()) {
            reader.moveDown();

            boolean critical = false;
            try {
                String fieldName = mapper.realMember(result.getClass(), reader.getNodeName());
                for (Class<?> concrete = result.getClass(); concrete != null; concrete = concrete.getSuperclass()) {
                    // Not quite right since a subclass could shadow a field, but probably suffices:
                    if (hasCriticalField(concrete, fieldName)) {
                        critical = true;
                        break;
                    }
                }
                boolean implicitCollectionHasSameName = mapper.getImplicitCollectionDefForFieldName(result.getClass(), reader.getNodeName()) != null;

                Class classDefiningField = determineWhichClassDefinesField(reader);
                boolean fieldExistsInClass = !implicitCollectionHasSameName && fieldDefinedInClass(result,fieldName);

                Class type = determineType(reader, fieldExistsInClass, result, fieldName, classDefiningField);
                final Object value;
                if (fieldExistsInClass) {
                    Field field = reflectionProvider.getField(result.getClass(),fieldName);
                    value = unmarshalField(context, result, type, field);
                    // TODO the reflection provider should have returned the proper field in first place ....
                    Class definedType = reflectionProvider.getFieldType(result, fieldName, classDefiningField);
                    if (!definedType.isPrimitive()) {
                        type = definedType;
                    }
                } else {
                    value = context.convertAnother(result, type);
                }

                if (value != null && !type.isAssignableFrom(value.getClass())) {
                    LOGGER.warning("Cannot convert type " + value.getClass().getName() + " to type " + type.getName());
                    // behave as if we didn't see this element
                } else {
                    if (fieldExistsInClass) {
                        reflectionProvider.writeField(result, fieldName, value, classDefiningField);
                        seenFields.add(classDefiningField, fieldName);
                    } else {
                        implicitCollectionsForCurrentObject = writeValueToImplicitCollection(context, value, implicitCollectionsForCurrentObject, result, fieldName);
                    }
                }
            } catch (CriticalXStreamException e) {
                throw e;
            } catch (XStreamException e) {
                if (critical) {
                    throw new CriticalXStreamException(e);
                }
                addErrorInContext(context, e);
            } catch (LinkageError e) {
                if (critical) {
                    throw e;
                }
                addErrorInContext(context, e);
            }

            reader.moveUp();
        }

        // Report any class/field errors in Saveable objects
        if (context.get("ReadError") != null && context.get("Saveable") == result) {
            // Avoid any error in OldDataMonitor to be catastrophic. See JENKINS-62231 and JENKINS-59582
            // The root cause is the OldDataMonitor extension is not ready before a plugin triggers an error, for 
            // example when trying to load a field that was created by a new version and you downgrade to the previous
            // one.
            try {
                OldDataMonitor.report((Saveable) result, (ArrayList<Throwable>) context.get("ReadError"));
            } catch (Throwable t) {
                // it should be already reported, but we report with INFO just in case
                StringBuilder message = new StringBuilder("There was a problem reporting unmarshalling field errors");
                Level level = Level.WARNING;
                if (t instanceof IllegalStateException && t.getMessage().contains("Expected 1 instance of " + OldDataMonitor.class.getName())) {
                    message.append(". Make sure this code is executed after InitMilestone.EXTENSIONS_AUGMENTED stage, for example in Plugin#postInitialize instead of Plugin#start");
                    level = Level.INFO; // it was reported when getting the singleton for OldDataMonitor
                }
                // it should be already reported, but we report with INFO just in case
                LOGGER.log(level, message.toString(), t);
            }
            context.put("ReadError", null);
        }
        return result;
    }