in api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/ObjectGraph.java [53:282]
public record ObjectGraph(
List<ObjectGraph.Object> objects,
List<ObjectGraph.Relation> relations) {
@Builder
public record Object(
@NonNull String id,
@NonNull String packageName,
@NonNull String name,
@NonNull Optional<String> stereotype,
@NonNull Optional<String> description,
List<ObjectGraph.Field> fields) {
/** @return {@code packageName + "." + name} */
public String fqName() {
return packageName + "." + name;
}
public ObjectBuilder asBuilder() {
return builder().id(id).packageName(packageName).name(name).stereotype(stereotype).description(description)
.fields(fields.stream()
.map(Field::copy)
.collect(Collectors.toCollection(ArrayList::new)));
}
public Object copy() {
return asBuilder().build();
}
}
@Builder
public record Field(
@NonNull String name,
@NonNull String elementTypeShortName,
boolean isPlural,
@NonNull Optional<String> description) {
public FieldBuilder asBuilder() {
return builder().name(name).elementTypeShortName(elementTypeShortName).isPlural(isPlural).description(description);
}
public Field copy() {
return asBuilder().build();
}
}
@Builder
public record Relation(
@NonNull RelationType relationType,
ObjectGraph.@NonNull Object from,
ObjectGraph.@NonNull Object to,
@NonNull String description, // usually the middle label
@NonNull String nearLabel,
@NonNull String farLabel) {
public String fromId() { return from.id(); }
public String toId() { return to.id(); }
public boolean isAssociation() { return relationType.isAssociationAny(); }
public StringOperator multiplicityNotation() {
return relationType.isOneToMany()
? _Strings.asSquareBracketed
: StringOperator.identity();
}
public String descriptionFormatted() {
return multiplicityNotation().apply(description);
}
public RelationBuilder asBuilder() {
return builder().relationType(relationType)
.from(from)
.to(to)
.description(description)
.nearLabel(nearLabel)
.farLabel(farLabel);
}
public Relation copy(final Map<String, ObjectGraph.Object> objectById) {
return asBuilder()
.from(objectById.get(fromId()))
.to(objectById.get(toId()))
.build();
}
}
public enum RelationType {
ONE_TO_ONE,
ONE_TO_MANY,
MERGED_ASSOCIATIONS,
BIDIR_ASSOCIATION,
INHERITANCE;
public boolean isOneToOne() { return this == ONE_TO_ONE; }
public boolean isOneToMany() { return this == ONE_TO_MANY; }
public boolean isMerged() { return this == MERGED_ASSOCIATIONS; }
public boolean isBidir() { return this == BIDIR_ASSOCIATION; }
public boolean isInheritance() { return this == INHERITANCE; }
public boolean isAssociationAny() { return this != INHERITANCE; }
}
public static interface Factory {
ObjectGraph create();
}
public static interface Transformer {
/**
* If called from {@link ObjectGraph#transform(Transformer)},
* given {@link ObjectGraph objGraph} is a defensive copy, that can safely be mutated.
*/
ObjectGraph transform(ObjectGraph objGraph);
}
/**
* Factory providing built in {@link Transformer}(s).
*/
public record Transformers() {
public static Transformer relationMerger() { return new ObjectGraphRelationMerger(); }
public static Transformer objectModifier(final @NonNull UnaryOperator<ObjectGraph.Object> modifier) {
return new ObjectGraphObjectModifier(modifier);
}
}
public static interface Renderer {
void render(StringBuilder sb, ObjectGraph objGraph);
}
public ObjectGraph() {
this(new ArrayList<>(), new ArrayList<>());
}
public static ObjectGraph create(final ObjectGraph.@NonNull Factory factory) {
return factory.create();
}
/**
* Passes a (deep clone) copy of this {@link ObjectGraph} to given {@link Transformer}
* and returns a transformed {@link ObjectGraph}.
* <p>
* Hence transformers are not required to create defensive copies.
*/
public ObjectGraph transform(final ObjectGraph.@Nullable Transformer transfomer) {
return transfomer!=null
? transfomer.transform(this.copy())
: this;
}
public String render(final ObjectGraph.@Nullable Renderer renderer) {
if(renderer==null) return "";
var sb = new StringBuilder();
renderer.render(sb, this);
return sb.toString();
}
public DataSource asDiagramDslSource(final ObjectGraph.@Nullable Renderer renderer) {
var dsl = render(renderer);
return dsl==null
? DataSource.empty()
: DataSource.ofStringUtf8(dsl);
}
public void writeDiagramDsl(final ObjectGraph.@Nullable Renderer renderer, final DataSink sink) {
var dsl = render(renderer);
if(dsl==null) return;
sink.writeAll(os->
os.write(dsl.getBytes(StandardCharsets.UTF_8)));
}
public void writeDiagramDsl(final ObjectGraph.@Nullable Renderer renderer, final File destinationDslFile) {
var dsl = render(renderer);
if(dsl==null) return;
DataSink.ofFile(destinationDslFile)
.writeAll(os->
os.write(dsl.getBytes(StandardCharsets.UTF_8)));
}
/**
* Returns a (deep clone) copy of this {@link ObjectGraph}.
*/
public ObjectGraph copy() {
var copy = new ObjectGraph();
this.objects().stream()
.map(ObjectGraph.Object::copy)
.forEach(copy.objects()::add);
var copyiedObjectById = copy.objectById();
this.relations().stream()
.map(rel->rel.copy(copyiedObjectById))
.forEach(copy.relations()::add);
return copy;
}
/**
* Returns a {@link GraphKernel} of given characteristics.
*/
public GraphKernel kernel(final @NonNull ImmutableEnumSet<GraphCharacteristic> characteristics) {
var kernel = new GraphUtils.GraphKernel(
objects().size(), characteristics);
relations().forEach(rel->{
kernel.addEdge(
objects().indexOf(rel.from()),
objects().indexOf(rel.to()));
});
return kernel;
}
/**
* Returns objects grouped by package (as list-multimap).
*/
public Map<String, List<ObjectGraph.Object>> objectsGroupedByPackage() {
final var objectsGroupedByPackage = _Multimaps.<String, ObjectGraph.Object>newListMultimap();
objects()
.forEach(obj->objectsGroupedByPackage.putElement(obj.packageName(), obj));
return objectsGroupedByPackage;
}
/**
* Returns a {@link Map} from object.id to {@link Object ObjectGraph.Object}
*/
public Map<String, ObjectGraph.Object> objectById() {
final var objectById = new HashMap<String, ObjectGraph.Object>();
objects()
.forEach(obj->objectById.put(obj.id(), obj));
return objectById;
}
/**
* Returns a sub-graph comprised only of object nodes as picked per zero based indexes {@code int[]}.
*/
public ObjectGraph subGraph(final int[] objectIndexes) {
var subGraph = this.transform(g->{
var subSet = Can.ofCollection(g.objects()).pickByIndex(objectIndexes);
g.objects().clear();
subSet.forEach(g.objects()::add);
var objectIds = g.objectById().keySet();
var isInSubgraph = (Predicate<ObjectGraph.Relation>) rel->
objectIds.contains(rel.fromId())
&& objectIds.contains(rel.toId());
g.relations().removeIf(isInSubgraph.negate());
return g;
});
return subGraph;
}
}