record _ObjectGraphFactory()

in core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/_ObjectGraphFactory.java [44:217]


record _ObjectGraphFactory(
        Collection<? extends ObjectSpecification> objectSpecifications,
        ObjectGraph objectGraph,
        ListMultimap<String, LogicalType> logicalTypesByNamespace,
        Map<LogicalType, ObjectGraph.Object> objectByLogicalType,
        Map<String, ObjectGraph.Object> objectById) implements ObjectGraph.Factory {


    public _ObjectGraphFactory(final List<? extends ObjectSpecification> objectSpecs) {
        this(objectSpecs, new ObjectGraph(), _Multimaps.newListMultimap(), new HashMap<>(), new HashMap<>());
    }

    @Override
    public final ObjectGraph create() {
        objectSpecifications.forEach(this::registerObject);

        // single use only! (we cannot call this repeatedly, as it adds more and more duplicates)
        objectGraph.relations().addAll(createInheritanceRelations());

        return objectGraph;
    }

    // -- HELPER

    private ObjectGraph.Object registerObject(final ObjectSpecification objSpec) {

        var addFieldsLater = _Refs.booleanRef(false);

        var obj = objectByLogicalType.computeIfAbsent(objSpec.logicalType(), logicalType->{
            logicalTypesByNamespace.putElement(logicalType.namespace(), logicalType);
            var newObjId = "o" + objectByLogicalType.size();
            var newObj = object(newObjId, objSpec);
            objectById.put(newObjId, newObj);
            addFieldsLater.setValue(true);
            objectGraph.objects().add(newObj);
            return newObj;
        });

        if(addFieldsLater.isTrue()) {
            objSpec.streamAssociations(MixedIn.EXCLUDED)
            .peek(ass->{
                var elementType = ass.getElementType();
                if(elementType.isEntity()
                        || elementType.isAbstract()) {
                    var referencedObj = registerObject(elementType);

                    var thisType = objSpec.logicalType();
                    var refType = elementType.logicalType();

                    var thisNs = thisType.namespace();
                    var refNs = refType.namespace();

                    // only register association relations if they don't cross namespace boundaries
                    // in other words: only include, if they share the same namespace
                    if(thisNs.equals(refNs)) {
                        var thisCls = thisType.correspondingClass();
                        var refCls = refType.correspondingClass();
                        if(thisCls.equals(refCls)
                                || !refCls.isAssignableFrom(thisCls)) {
                            // we found a 1-x relation
                            registerRelation(
                                    ass.isOneToOneAssociation()
                                        ? ObjectGraph.RelationType.ONE_TO_ONE
                                        : ObjectGraph.RelationType.ONE_TO_MANY,
                                    obj.id(), referencedObj.id(), ass.getId());
                        }
                    }

                }
            })
            .map(this::fieldForAss)
            .forEach(obj.fields()::add);
        }
        return obj;
    }

    private ObjectGraph.Relation registerRelation(
            final ObjectGraph.RelationType relationType,
            final String fromId,
            final String toId,
            final String description) {
        var relation = new ObjectGraph.Relation(
                relationType,
                objectById.get(fromId),
                objectById.get(toId),
                description,
                "", "");
        objectGraph.relations().add(relation);
        return relation;
    }

    private static ObjectGraph.Object object(final String id, final ObjectSpecification objSpec) {
        var obj =  new ObjectGraph.Object(id,
                objSpec.logicalType().namespace(),
                objSpec.logicalType().logicalSimpleName(),
                objSpec.isAbstract()
                    ? Optional.of("abstract")
                    : Optional.empty(),
                Optional.ofNullable(objSpec.getDescription()),
                new ArrayList<>());
        return obj;
    }

    private ObjectGraph.Field fieldForAss(final ObjectAssociation ass) {
        return new ObjectGraph.Field(
                ass.getId(),
                objectShortName(ass.getElementType()),
                ass.isOneToManyAssociation(),
                ass.getStaticDescription());
    }

    private Set<Relation> createInheritanceRelations() {

        final Set<ObjectGraph.Relation> inheritanceRelations = new HashSet<>();
        final Set<ObjectGraph.Relation> markedForRemoval = new HashSet<>();

        for(var e1 : objectByLogicalType.entrySet()) {
            for(var e2 : objectByLogicalType.entrySet()) {
                var type1 = e1.getKey();
                var type2 = e2.getKey();
                if(type1.equals(type2)) continue;
                var cls1 = type1.correspondingClass();
                var cls2 = type2.correspondingClass();
                if(cls2.isAssignableFrom(cls1)) {
                    var o1 = e1.getValue();
                    var o2 = e2.getValue();
                    // we found an inheritance relation
                    var relation = new ObjectGraph.Relation(
                            ObjectGraph.RelationType.INHERITANCE,
                            objectById.get(o1.id()), objectById.get(o2.id()), "", "", "");
                    inheritanceRelations.add(relation);
                }
            }
        }

        // remove any inheritance relations that shortcut others
        outer:
        for(var r1 : inheritanceRelations) {
            for(var r2 : inheritanceRelations) {
                if(r1==r2) continue;
                if(!r1.fromId().equals(r2.fromId())) continue;
                for(var r3 : inheritanceRelations) {
                    if(r1==r3) continue;
                    if(r2==r3) continue;
                    if(!r1.toId().equals(r3.toId())) continue;
                    if(!r2.toId().equals(r3.fromId())) continue;

                    /*
                     * If there exists a non-direct path from [r1.from] to [r1.to],
                     * than r1 needs to be marked for removal.
                     *
                     * It is sufficient to check for paths of length 2 specifically.
                     *
                     * Such a path is found, if following condition is true
                     * [r1.from] == [r2.from]
                     * && [r1.to] == [r3.to]
                     * && [r2.to] == [r3.from].
                     */

                    markedForRemoval.add(r1);
                    continue outer;
                }
            }
        }

        return _Sets.minus(inheritanceRelations, markedForRemoval);
    }

    private static String objectShortName(final ObjectSpecification objSpec) {
        var simpleName = objSpec.logicalType().logicalSimpleName();
        return simpleName;
    }

}