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

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

The newest version!
package com.kjetland.jackson.jsonSchema;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
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.AccessLevel;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;

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

    // can be records
    @Data @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @Accessors(fluent = true)
    static class DefinitionInfo {
    	String ref;
    	JsonObjectFormatVisitor jsonObjectFormatVisitor;
    	DefinitionInfo (String ref, JsonObjectFormatVisitor jsonObjectFormatVisitor) {
    		this.ref=ref;
    		this.jsonObjectFormatVisitor=jsonObjectFormatVisitor;
    	}
    }
    @Data @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @Accessors(fluent = true)
    static class WorkInProgress {
    	JavaType typeInProgress;
    	ObjectNode nodeInProgress;
    	WorkInProgress(JavaType typeInProgress, ObjectNode nodeInProgress) {
    		this.typeInProgress=typeInProgress;
    		this.nodeInProgress=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 {

        String ref = class2Ref.get(type);
        if (ref != null)
            // Return existing definition
            if (!workInProgress.isPresent())
                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);

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

        ObjectNode node = JsonNodeFactory.instance.objectNode();

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

        JsonObjectFormatVisitor 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) {
        String baseName = config.useTypeIdForDefinitionName
                ? type.getRawClass().getTypeName()
                : Utils.extractTypeName(type);

        if (type.hasGenericTypes()) {
            String 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