// Path: src/main/java/ioinformarics/oss/jackson/module/jsonld/util/AnnotationsUtils.java
// public abstract class AnnotationsUtils {
// 
//     public static boolean isAnnotationPresent(Class<?> type, Class<? extends Annotation> annotationType) {
//         return isAnnotationPresent(type, annotationType, new ArrayList<>());
//     }
// 
//     protected static boolean isAnnotationPresent(Class<?> type, Class<? extends Annotation> annotationType, List<Class<?>> ignore) {
//         if(type.isAnnotationPresent(annotationType)) {
//             return true;
//         }
//         if(type.getAnnotations().length == 0) {
//             return false;
//         }
//         for(Annotation a : type.getAnnotations()) {
//             if(!ignore.contains(a.annotationType())) {
//                 ignore.add(type);
//                 if(isAnnotationPresent(a.annotationType(), annotationType, ignore)) {
//                     return true;
//                 }
//             }
//         }
//         return false;
//     }
// 
// }
// 
// Path: src/main/java/ioinformarics/oss/jackson/module/jsonld/util/JsonUtils.java
// public abstract class JsonUtils {
// 
//     public static JsonNode merge(JsonNode mainNode, JsonNode updateNode) {
// 
//         Iterator<String> fieldNames = updateNode.fieldNames();
//         while (fieldNames.hasNext()) {
// 
//             String fieldName = fieldNames.next();
//             JsonNode jsonNode = mainNode.get(fieldName);
//             // if field exists and is an embedded object
//             if (jsonNode != null && jsonNode.isObject()) {
//                 merge(jsonNode, updateNode.get(fieldName));
//             }
//             else {
//                 if (mainNode instanceof ObjectNode) {
//                     // Overwrite field
//                     JsonNode value = updateNode.get(fieldName);
//                     ((ObjectNode) mainNode).set(fieldName, value);
//                 }
//             }
// 
//         }
// 
//         return mainNode;
//     }
// }
// 
// Path: src/main/java/ioinformarics/oss/jackson/module/jsonld/util/JsonldResourceUtils.java
// public abstract class JsonldResourceUtils {
// 
//     public static Optional<ObjectNode> getContext(Object scopedObj) {
//         return JsonldContextFactory.fromAnnotations(scopedObj);
//     }
// 
//     public static Optional<String> dynamicTypeLookup(Class<?> objType){
//         Optional<String> typeFromAnnotation = Optional.ofNullable(objType.getAnnotation(JsonldType.class))
//                     .map(JsonldType::value);
//         return typeFromAnnotation.isPresent() ? typeFromAnnotation : typeFromJavaClass(objType);
//     }
// 
//     private static Optional<String> typeFromJavaClass(Class<?> objType) {
//         return Optional.ofNullable(objType.getAnnotation(JsonldTypeFromJavaClass.class))
//                 .map((t) -> {
//                     String prefix = t.namespace();
//                     if(prefix.equals(AnnotationConstants.UNASSIGNED)) {
//                         prefix = t.namespacePrefix().equals(AnnotationConstants.UNASSIGNED) ? "" : t.namespacePrefix() + ":";
//                     }
//                     return prefix + objType.getSimpleName();
// 
//                 });
//     }
// }


import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import ioinformarics.oss.jackson.module.jsonld.annotation.*;
import ioinformarics.oss.jackson.module.jsonld.util.AnnotationsUtils;
import ioinformarics.oss.jackson.module.jsonld.util.JsonUtils;
import ioinformarics.oss.jackson.module.jsonld.util.JsonldResourceUtils;
import org.apache.commons.lang3.ClassUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Stream;

    private static boolean isRelation(Field field) {
        Class<?> type = relationType(field);
        return Stream.concat(Stream.of(type),
                Stream.concat(ClassUtils.getAllSuperclasses(type).stream(), ClassUtils.getAllInterfaces(type).stream()))
                .flatMap(currentClass -> Stream.concat(
                        Stream.of(currentClass.getDeclaredFields()),
                        Stream.of(currentClass.getDeclaredMethods())
                ))
                .anyMatch(p -> p.getAnnotation(JsonldId.class) != null);
    }

    private static Map<String, String> generateNamespaces(Class<?> objType) {
        JsonldNamespace[] namespaceAnnotations = objType.getAnnotationsByType(JsonldNamespace.class);
        Map<String, String> namespaces = new HashMap<>(namespaceAnnotations.length);
        Arrays.asList(namespaceAnnotations).forEach((ns) -> namespaces.put(ns.name(), ns.uri()));
        return namespaces;
    }


    public static Optional<JsonNode> multiContext(Optional<String> externalContext,
                                                  Optional<ObjectNode> internalContext) {
        if (internalContext.isPresent()) {
            return externalContext.isPresent() ?
                   Optional.of((JsonNode) buildMultiContext(externalContext.get(), internalContext.get())) :
                   internalContext.map(it -> (JsonNode) it);
        }
        return externalContext.map(TextNode::valueOf);
    }

// Path: src/main/java/ioinformarics/oss/jackson/module/jsonld/util/AnnotationsUtils.java
// public abstract class AnnotationsUtils {
// 
//     public static boolean isAnnotationPresent(Class<?> type, Class<? extends Annotation> annotationType) {
//         return isAnnotationPresent(type, annotationType, new ArrayList<>());
//     }
// 
//     protected static boolean isAnnotationPresent(Class<?> type, Class<? extends Annotation> annotationType, List<Class<?>> ignore) {
//         if(type.isAnnotationPresent(annotationType)) {
//             return true;
//         }
//         if(type.getAnnotations().length == 0) {
//             return false;
//         }
//         for(Annotation a : type.getAnnotations()) {
//             if(!ignore.contains(a.annotationType())) {
//                 ignore.add(type);
//                 if(isAnnotationPresent(a.annotationType(), annotationType, ignore)) {
//                     return true;
//                 }
//             }
//         }
//         return false;
//     }
// 
// }
// 
// Path: src/main/java/ioinformarics/oss/jackson/module/jsonld/util/JsonUtils.java
// public abstract class JsonUtils {
// 
//     public static JsonNode merge(JsonNode mainNode, JsonNode updateNode) {
// 
//         Iterator<String> fieldNames = updateNode.fieldNames();
//         while (fieldNames.hasNext()) {
// 
//             String fieldName = fieldNames.next();
//             JsonNode jsonNode = mainNode.get(fieldName);
//             // if field exists and is an embedded object
//             if (jsonNode != null && jsonNode.isObject()) {
//                 merge(jsonNode, updateNode.get(fieldName));
//             }
//             else {
//                 if (mainNode instanceof ObjectNode) {
//                     // Overwrite field
//                     JsonNode value = updateNode.get(fieldName);
//                     ((ObjectNode) mainNode).set(fieldName, value);
//                 }
//             }
// 
//         }
// 
//         return mainNode;
//     }
// }
// 
// Path: src/main/java/ioinformarics/oss/jackson/module/jsonld/util/JsonldResourceUtils.java
// public abstract class JsonldResourceUtils {
// 
//     public static Optional<ObjectNode> getContext(Object scopedObj) {
//         return JsonldContextFactory.fromAnnotations(scopedObj);
//     }
// 
//     public static Optional<String> dynamicTypeLookup(Class<?> objType){
//         Optional<String> typeFromAnnotation = Optional.ofNullable(objType.getAnnotation(JsonldType.class))
//                     .map(JsonldType::value);
//         return typeFromAnnotation.isPresent() ? typeFromAnnotation : typeFromJavaClass(objType);
//     }
// 
//     private static Optional<String> typeFromJavaClass(Class<?> objType) {
//         return Optional.ofNullable(objType.getAnnotation(JsonldTypeFromJavaClass.class))
//                 .map((t) -> {
//                     String prefix = t.namespace();
//                     if(prefix.equals(AnnotationConstants.UNASSIGNED)) {
//                         prefix = t.namespacePrefix().equals(AnnotationConstants.UNASSIGNED) ? "" : t.namespacePrefix() + ":";
//                     }
//                     return prefix + objType.getSimpleName();
// 
//                 });
//     }
// }


// Path: src/main/java/ioinformarics/oss/jackson/module/jsonld/JsonldContextFactory.java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import ioinformarics.oss.jackson.module.jsonld.annotation.*;
import ioinformarics.oss.jackson.module.jsonld.util.AnnotationsUtils;
import ioinformarics.oss.jackson.module.jsonld.util.JsonUtils;
import ioinformarics.oss.jackson.module.jsonld.util.JsonldResourceUtils;
import org.apache.commons.lang3.ClassUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Stream;


    private static boolean isRelation(Field field) {
        Class<?> type = relationType(field);
        return Stream.concat(Stream.of(type),
                Stream.concat(ClassUtils.getAllSuperclasses(type).stream(), ClassUtils.getAllInterfaces(type).stream()))
                .flatMap(currentClass -> Stream.concat(
                        Stream.of(currentClass.getDeclaredFields()),
                        Stream.of(currentClass.getDeclaredMethods())
                ))
                .anyMatch(p -> p.getAnnotation(JsonldId.class) != null);
    }

    private static Map<String, String> generateNamespaces(Class<?> objType) {
        JsonldNamespace[] namespaceAnnotations = objType.getAnnotationsByType(JsonldNamespace.class);
        Map<String, String> namespaces = new HashMap<>(namespaceAnnotations.length);
        Arrays.asList(namespaceAnnotations).forEach((ns) -> namespaces.put(ns.name(), ns.uri()));
        return namespaces;
    }


    public static Optional<JsonNode> multiContext(Optional<String> externalContext,
                                                  Optional<ObjectNode> internalContext) {
        if (internalContext.isPresent()) {
            return externalContext.isPresent() ?
                   Optional.of((JsonNode) buildMultiContext(externalContext.get(), internalContext.get())) :
                   internalContext.map(it -> (JsonNode) it);
        }
        return externalContext.map(TextNode::valueOf);
    }

    private static ArrayNode buildMultiContext(String context, JsonNode generatedContext) {