diff --git a/subprojects/generator-cli/build.gradle.kts b/subprojects/generator-cli/build.gradle.kts
index acfe7e50e..e1a5ce772 100644
--- a/subprojects/generator-cli/build.gradle.kts
+++ b/subprojects/generator-cli/build.gradle.kts
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2024 The Refinery Authors
+ * SPDX-FileCopyrightText: 2024-2026 The Refinery Authors
*
* SPDX-License-Identifier: EPL-2.0
*/
@@ -11,6 +11,7 @@ plugins {
dependencies {
implementation(project(":refinery-generator"))
implementation(libs.jcommander)
+ implementation(libs.gson)
implementation(libs.slf4j)
testRuntimeOnly(libs.slf4j.simple)
}
diff --git a/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java
index 5c7014aae..3af790125 100644
--- a/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java
+++ b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors
+ * SPDX-FileCopyrightText: 2023-2026 The Refinery Authors
*
* SPDX-License-Identifier: EPL-2.0
*/
@@ -17,6 +17,9 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import java.io.FileWriter;
@Parameters(commandDescription = "Generate a model from a partial model")
public class GenerateCommand implements Command {
@@ -30,6 +33,7 @@ public class GenerateCommand implements Command {
private List overrideScopes = new ArrayList<>();
private long randomSeed = 1;
private int count = 1;
+ private boolean outputJson;
@Inject
public GenerateCommand(CliProblemLoader loader, ModelGeneratorFactory generatorFactory,
@@ -72,6 +76,11 @@ public void setCount(int count) {
this.count = count;
}
+ @Parameter(names = {"-json", "-j"}, description = "Output as JSON")
+ public void setOutputJson(boolean outputJson) {
+ this.outputJson = outputJson;
+ }
+
@Override
public int run() throws IOException {
if (count > 1 && CliUtils.isStandardStream(outputPath)) {
@@ -83,16 +92,35 @@ public int run() throws IOException {
generator.setRandomSeed(randomSeed);
generator.setMaxNumberOfSolutions(count);
generator.generate();
- if (count == 1) {
- serializer.saveModel(generator, outputPath);
- } else {
- int solutionCount = generator.getSolutionCount();
- for (int i = 0; i < solutionCount; i++) {
- generator.loadSolution(i);
- var pathWithIndex = CliUtils.getFileNameWithIndex(outputPath, i + 1);
- serializer.saveModel(generator, pathWithIndex, false);
- }
- }
+ if (outputJson) {
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ if (count == 1) {
+ try (FileWriter writer = new FileWriter(outputPath)) {
+ gson.toJson(generator.getObjectNet(), writer);
+ }
+ } else {
+ int solutionCount = generator.getSolutionCount();
+ for (int i = 0; i < solutionCount; i++) {
+ generator.loadSolution(i);
+ var pathWithIndex = CliUtils.getFileNameWithIndex(outputPath, i + 1);
+ try (FileWriter writer = new FileWriter(pathWithIndex)) {
+ gson.toJson(generator.getObjectNet(), writer);
+ }
+ }
+ }
+ } else {
+ if (count == 1) {
+ serializer.saveModel(generator, outputPath);
+ } else {
+ int solutionCount = generator.getSolutionCount();
+ for (int i = 0; i < solutionCount; i++) {
+ generator.loadSolution(i);
+ var pathWithIndex = CliUtils.getFileNameWithIndex(outputPath, i + 1);
+ serializer.saveModel(generator, pathWithIndex, false);
+ }
+ }
+ }
+
}
return RefineryCli.EXIT_SUCCESS;
}
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ModelFacade.java b/subprojects/generator/src/main/java/tools/refinery/generator/ModelFacade.java
index cc8f58b76..ea50f00e4 100644
--- a/subprojects/generator/src/main/java/tools/refinery/generator/ModelFacade.java
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/ModelFacade.java
@@ -1,10 +1,11 @@
/*
- * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors
+ * SPDX-FileCopyrightText: 2023-2026 The Refinery Authors
*
* SPDX-License-Identifier: EPL-2.0
*/
package tools.refinery.generator;
+import tools.refinery.generator.dto.ObjectNetDto;
import tools.refinery.language.model.problem.Problem;
import tools.refinery.language.semantics.ProblemTrace;
import tools.refinery.language.semantics.metadata.NodesMetadata;
@@ -48,6 +49,8 @@ , C> PartialInterpretation getPartialInterpr
ConsistencyCheckResult checkConsistency();
+ ObjectNetDto getObjectNet();
+
Problem serialize();
Optional trySerialize();
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/dto/InstanceDto.java b/subprojects/generator/src/main/java/tools/refinery/generator/dto/InstanceDto.java
new file mode 100644
index 000000000..2f61b11c9
--- /dev/null
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/dto/InstanceDto.java
@@ -0,0 +1,63 @@
+/*
+ * SPDX-FileCopyrightText: 2026 The Refinery Authors
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package tools.refinery.generator.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InstanceDto {
+ private final Integer id;
+ private final String name;
+ private final String principalType;
+ private final transient TypeDto principalTypeObject;
+ private final List types = new ArrayList<>();
+ private final transient List typeObjects = new ArrayList<>();
+ private final transient List outgoingRelations = new ArrayList<>();
+ private final transient List incomingRelations = new ArrayList<>();
+
+ public InstanceDto(Integer id, TypeDto principalType, String name) {
+ this.id = id;
+ this.name = name;
+ this.principalTypeObject = principalType;
+ this.principalType = principalType.getName();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public TypeDto getPrincipalTypeObject() {
+ return principalTypeObject;
+ }
+
+ public List getTypes() {
+ return types;
+ }
+
+ public void addType(TypeDto type) {
+ typeObjects.add(type);
+ types.add(type.getName());
+ }
+
+ public void addTypes(List types) {
+ for (TypeDto type : types) {
+ addType(type);
+ }
+ }
+
+ public List getIncomingRelations() {
+ return incomingRelations;
+ }
+
+ public List getOutgoingRelations() {
+ return outgoingRelations;
+ }
+}
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/dto/ObjectNetDto.java b/subprojects/generator/src/main/java/tools/refinery/generator/dto/ObjectNetDto.java
new file mode 100644
index 000000000..464552ce0
--- /dev/null
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/dto/ObjectNetDto.java
@@ -0,0 +1,39 @@
+/*
+ * SPDX-FileCopyrightText: 2026 The Refinery Authors
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package tools.refinery.generator.dto;
+
+import java.util.List;
+
+public class ObjectNetDto {
+ private final List types;
+ private final List relationTypes;
+ private final List instances;
+ private final List relationInstances;
+
+ public ObjectNetDto(List types, List relationTypes, List instances, List relationInstances) {
+ this.types = types;
+ this.relationTypes = relationTypes;
+ this.instances = instances;
+ this.relationInstances = relationInstances;
+ }
+
+ public List getTypes() {
+ return types;
+ }
+
+ public List getRelationTypes() {
+ return relationTypes;
+ }
+
+ public List getInstances() {
+ return instances;
+ }
+
+ public List getRelationInstances() {
+ return relationInstances;
+ }
+}
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/dto/RelationInstanceDto.java b/subprojects/generator/src/main/java/tools/refinery/generator/dto/RelationInstanceDto.java
new file mode 100644
index 000000000..003a906e4
--- /dev/null
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/dto/RelationInstanceDto.java
@@ -0,0 +1,46 @@
+/*
+ * SPDX-FileCopyrightText: 2026 The Refinery Authors
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package tools.refinery.generator.dto;
+
+import tools.refinery.store.reasoning.representation.PartialRelation;
+
+public class RelationInstanceDto {
+
+ private final transient PartialRelation relation;
+ private final transient RelationTypeDto type;
+ private final transient InstanceDto source;
+ private final transient InstanceDto target;
+
+ private final String relationType;
+ private final Integer sourceId;
+ private final Integer targetId;
+
+ public RelationInstanceDto(PartialRelation relation, RelationTypeDto type, InstanceDto source, InstanceDto target) {
+ this.relation = relation;
+ this.type = type;
+ this.source = source;
+ this.target = target;
+ this.relationType = type.getName();
+ this.sourceId = source.getId();
+ this.targetId = target.getId();
+
+ source.getOutgoingRelations().add(this);
+ target.getIncomingRelations().add(this);
+ }
+
+ public RelationTypeDto getType() {
+ return type;
+ }
+
+ public InstanceDto getSource() {
+ return source;
+ }
+
+ public InstanceDto getTarget() {
+ return target;
+ }
+}
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/dto/RelationTypeDto.java b/subprojects/generator/src/main/java/tools/refinery/generator/dto/RelationTypeDto.java
new file mode 100644
index 000000000..4e9501a44
--- /dev/null
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/dto/RelationTypeDto.java
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: 2026 The Refinery Authors
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package tools.refinery.generator.dto;
+
+import tools.refinery.store.reasoning.representation.PartialRelation;
+
+public class RelationTypeDto {
+
+ public enum Kind {
+ CONTAINMENT,
+ DIRECTED_CROSS_REF,
+ UNDIRECTED_CROSS_REF,
+ OPPOSITE
+ }
+
+ private final transient PartialRelation relation;
+ private final Kind kind;
+ private final String name;
+ private final transient TypeDto source;
+ private final transient TypeDto target;
+ private final String sourceName;
+ private final String targetName;
+
+ public RelationTypeDto(PartialRelation relation, Kind kind, String name, TypeDto source, TypeDto target) {
+ this.relation = relation;
+ this.kind = kind;
+ this.name = name.substring(name.lastIndexOf(":") + 1);
+ this.source = source;
+ this.target = target;
+ this.sourceName = source.getName();
+ this.targetName = target.getName();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public TypeDto getSource() {
+ return source;
+ }
+
+ public TypeDto getTarget() {
+ return target;
+ }
+
+ public PartialRelation getRelation() {
+ return relation;
+ }
+
+ public Kind getKind() {
+ return kind;
+ }
+}
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/dto/TypeDto.java b/subprojects/generator/src/main/java/tools/refinery/generator/dto/TypeDto.java
new file mode 100644
index 000000000..2a066ff6b
--- /dev/null
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/dto/TypeDto.java
@@ -0,0 +1,50 @@
+/*
+ * SPDX-FileCopyrightText: 2026 The Refinery Authors
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package tools.refinery.generator.dto;
+
+import tools.refinery.store.reasoning.representation.PartialRelation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TypeDto {
+ private final transient PartialRelation relation;
+ private final String name;
+ private final List superTypes = new ArrayList<>();
+ private final transient List superTypeObjects = new ArrayList<>();
+ private final transient List subTypeObjects = new ArrayList<>();
+
+ public TypeDto(PartialRelation relation, String name) {
+ this.relation = relation;
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public PartialRelation getPartialRelation() {
+ return relation;
+ }
+
+ public List getSuperTypes() {
+ return superTypes;
+ }
+
+ public List getSuperTypeObjects() {
+ return superTypeObjects;
+ }
+
+ public void addSuperType(TypeDto type) {
+ superTypeObjects.add(type);
+ superTypes.add(type.getName());
+ }
+
+ public void addSubType(TypeDto type) {
+ subTypeObjects.add(type);
+ }
+}
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/impl/ModelFacadeImpl.java b/subprojects/generator/src/main/java/tools/refinery/generator/impl/ModelFacadeImpl.java
index 7ebf62d59..fe69f049d 100644
--- a/subprojects/generator/src/main/java/tools/refinery/generator/impl/ModelFacadeImpl.java
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/impl/ModelFacadeImpl.java
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors
+ * SPDX-FileCopyrightText: 2023-2026 The Refinery Authors
*
* SPDX-License-Identifier: EPL-2.0
*/
@@ -36,6 +36,15 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import tools.refinery.generator.dto.*;
+import tools.refinery.store.tuple.Tuple;
+import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator;
+import tools.refinery.store.reasoning.representation.PartialRelation;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
+
public abstract class ModelFacadeImpl implements ModelFacade {
private final ProblemTrace problemTrace;
@@ -192,6 +201,99 @@ private , C> void checkConsistency(
}
}
+ public List getAllSuperTypesOfType(TypeDto type) {
+ if (type.getSuperTypeObjects().isEmpty()) {
+ List supertypes = new ArrayList<>();
+ supertypes.add(type);
+ return supertypes;
+ } else {
+ List supertypes = new ArrayList<>();
+ for (TypeDto superType : type.getSuperTypeObjects()) {
+ supertypes.addAll(getAllSuperTypesOfType(superType));
+ }
+ supertypes.add(type);
+ return supertypes;
+ }
+ }
+
+ @Override
+ public ObjectNetDto getObjectNet() {
+ Map typeMap = new HashMap<>();
+ Map relationTypeMap = new HashMap<>();
+ Map instanceMap = new HashMap<>();
+ Map relationInstanceMap = new HashMap<>();
+
+ var trace = getProblemTrace();
+ var metamodel = trace.getMetamodel();
+
+ metamodel.typeHierarchy().getAllTypes().forEach(type -> typeMap.put(type, new TypeDto(type, type.name())));
+
+ typeMap.forEach((key, value) -> {
+ for (PartialRelation subType : metamodel.typeHierarchy().getAnalysisResult(key).getDirectSubtypes()) {
+ TypeDto subTypeObj = typeMap.get(subType);
+ value.addSubType(subTypeObj);
+ subTypeObj.addSuperType(value);
+ }
+ });
+
+ metamodel.containmentHierarchy().forEach((rel, info) -> relationTypeMap.put(rel,
+ new RelationTypeDto(rel, RelationTypeDto.Kind.CONTAINMENT, rel.name(), typeMap.get(info.sourceType()),
+ typeMap.get(info.targetType()))));
+ metamodel.directedCrossReferences().forEach((rel, info) -> relationTypeMap.put(rel,
+ new RelationTypeDto(rel, RelationTypeDto.Kind.DIRECTED_CROSS_REF, rel.name(),
+ typeMap.get(info.sourceType()), typeMap.get(info.targetType()))));
+
+ var nodeTrace = trace.getNodeTrace();
+ var typeInterpretation = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL);
+ var typeInterpretaionCursor = typeInterpretation.getAll();
+ while (typeInterpretaionCursor.move()) {
+ final var cursor = typeInterpretaionCursor;
+ typeMap.forEach((key, value) -> {
+ if (value.getName().equals(cursor.getValue().candidateType().name()) &&
+ cursor.getValue().isMust(cursor.getValue().candidateType())) {
+ int nodeId = cursor.getKey().get(0);
+ var nodes = nodeTrace.flipUniqueValues().get(nodeId);
+ String name = (nodes != null) ? nodes.getName() : "";
+ if ("new".equals(name) || "".equals(name)) {
+ name = value.getName() + nodeId;
+ }
+ instanceMap.put(nodeId, new InstanceDto(nodeId, value, name));
+ }
+ });
+ }
+
+ instanceMap.forEach((id, instance) -> {
+ TypeDto principalType = instance.getPrincipalTypeObject();
+ for (TypeDto superType : principalType.getSuperTypeObjects()) {
+ instance.addTypes(getAllSuperTypesOfType(superType));
+ }
+ });
+
+ for (Map.Entry entry : relationTypeMap.entrySet()) {
+ var relationInterpretation = getPartialInterpretation(entry.getKey());
+ var relationCursor = relationInterpretation.getAll();
+ while (relationCursor.move()) {
+ if (relationCursor.getValue() == TruthValue.TRUE) {
+ Tuple pair = relationCursor.getKey();
+ InstanceDto sourceInstance = instanceMap.get(pair.get(0));
+ InstanceDto targetInstance = instanceMap.get(pair.get(1));
+ if (sourceInstance != null && targetInstance != null) {
+ relationInstanceMap.put(pair,
+ new RelationInstanceDto(entry.getKey(), entry.getValue(), sourceInstance,
+ targetInstance));
+ }
+ }
+ }
+ }
+
+ return new ObjectNetDto(
+ new ArrayList<>(typeMap.values()),
+ new ArrayList<>(relationTypeMap.values()),
+ new ArrayList<>(instanceMap.values()),
+ new ArrayList<>(relationInstanceMap.values())
+ );
+ }
+
@Override
public Optional trySerialize() {
return Optional.of(serialize());