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());