All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kjetland.jackson.jsonSchema.DefinitionsHandler Maven / Gradle / Ivy

There is a newer version: 1.1.2
Show newest version
package com.kjetland.jackson.jsonSchema;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import lombok.RequiredArgsConstructor;

// Class that manages creating new definitions or getting $refs to existing definitions
@RequiredArgsConstructor
class DefinitionsHandler {

    record DefinitionInfo(String ref, JsonObjectFormatVisitor jsonObjectFormatVisitor) {}
    record WorkInProgress(JavaType typeInProgress, ObjectNode nodeInProgress) {}
    
    final JsonSchemaConfig config;

    final private Map class2Ref = new HashMap<>();
    final private ObjectNode definitionsNode = JsonNodeFactory.instance.objectNode();

    // Used to combine multiple invocations of `getOrCreateDefinition` when processing polymorphism.
    private Deque> workInProgressStack = new LinkedList<>();
    private Optional workInProgress = Optional.empty();

    
    @FunctionalInterface
    interface VisitorSupplier {
        
        JsonObjectFormatVisitor get(JavaType type, ObjectNode t) throws JsonMappingException;
    }

    public void pushWorkInProgress() {
        workInProgressStack.push(workInProgress);
        workInProgress = Optional.empty();
    }

    public void popworkInProgress() {
        workInProgress = workInProgressStack.pop();
    }

    // Either creates new definitions or return $ref to existing one
    public DefinitionInfo getOrCreateDefinition(JavaType type, VisitorSupplier visitorSupplier) throws JsonMappingException {

        var ref = class2Ref.get(type);
        if (ref != null)
            // Return existing definition
            if (workInProgress.isEmpty())
                return new DefinitionInfo(ref, null);
            else {
                // this is a recursive polymorphism call
                if (type != workInProgress.get().typeInProgress)
                    throw new IllegalStateException("Wrong type - working on " + workInProgress.get().typeInProgress + " - got " + type);

                var visitor = visitorSupplier.get(type, workInProgress.get().nodeInProgress);
                return new DefinitionInfo(null, visitor);
            }
            
        // Build new definition
        var retryCount = 0;
        var definitionName = getDefinitionName(type);
        var shortRef = definitionName;
        var longRef = "#/definitions/" + definitionName;
        while (class2Ref.containsValue(longRef)) {
            retryCount = retryCount + 1;
            shortRef = definitionName + "_" + retryCount;
            longRef = "#/definitions/" + definitionName + "_" + retryCount;
        }
        class2Ref.put(type, longRef);

        var node = JsonNodeFactory.instance.objectNode();

        // When processing polymorphism, we might get multiple recursive calls to getOrCreateDefinition - this is a way to combine them
        workInProgress = Optional.of(new WorkInProgress(type, node));
        definitionsNode.set(shortRef, node);

        var visitor = visitorSupplier.get(type, node);

        workInProgress = Optional.empty();

        return new DefinitionInfo(longRef, visitor);
    }

    public ObjectNode getFinalDefinitionsNode() {
        if (class2Ref.isEmpty())
            return null;
        else
            return definitionsNode;
    }

    private String getDefinitionName(JavaType type) {
        var baseName = config.useTypeIdForDefinitionName
                ? type.getRawClass().getTypeName()
                : Utils.extractTypeName(type);

        if (type.hasGenericTypes()) {
            var containedTypeNames
                    = IntStream.range(0, type.containedTypeCount())
                            .mapToObj(type::containedType)
                            .map(this::getDefinitionName)
                            .collect(Collectors.joining(","));
            return baseName + "(" + containedTypeNames + ")";
        } else {
            return baseName;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy