This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/master by this push:
new 75155849cb CAUSEWAY-3404: quality of life improvements for ObjectGraph
75155849cb is described below
commit 75155849cb216c3c87f271962aa71f1ad32d6e83
Author: Andi Huber <[email protected]>
AuthorDate: Fri Jan 19 07:53:22 2024 +0100
CAUSEWAY-3404: quality of life improvements for ObjectGraph
- adds object and field descriptions
- export utilities
- also adds some string utilities
---
.../services/metamodel/objgraph/ObjectGraph.java | 56 +++++++++++++++++-----
.../objgraph/_ObjectGraphRelationMerger.java | 35 ++++++++++----
.../causeway/commons/internal/base/_Strings.java | 36 ++++++++++++--
.../org/apache/causeway/commons/io/DataSource.java | 9 ++++
.../services/metamodel/_ObjectGraphFactory.java | 14 ++++--
.../objgraph/d3js/ObjectGraphRendererD3js.java | 2 +-
.../plantuml/ObjectGraphRendererPlantuml.java | 5 +-
7 files changed, 126 insertions(+), 31 deletions(-)
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/ObjectGraph.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/ObjectGraph.java
index ed18837836..be43ee484e 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/ObjectGraph.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/ObjectGraph.java
@@ -18,6 +18,8 @@
*/
package org.apache.causeway.applib.services.metamodel.objgraph;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -26,7 +28,11 @@ import java.util.Optional;
import org.springframework.lang.Nullable;
+import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.commons.internal.base._Strings.StringOperator;
import org.apache.causeway.commons.internal.collections._Multimaps;
+import org.apache.causeway.commons.io.DataSink;
+import org.apache.causeway.commons.io.DataSource;
import lombok.Getter;
import lombok.NonNull;
@@ -49,6 +55,7 @@ public class ObjectGraph {
private final @With @NonNull String packageName;
private final @With @NonNull String name;
private final @With @NonNull Optional<String> stereotype;
+ private final @With @NonNull Optional<String> description;
private final @With List<ObjectGraph.Field> fields;
}
@@ -57,6 +64,7 @@ public class ObjectGraph {
private final @With @NonNull String name;
private final @With @NonNull String elementTypeShortName;
private final @With boolean isPlural;
+ private final @With @NonNull Optional<String> description;
}
@lombok.Value @Accessors(fluent=true)
@@ -64,27 +72,34 @@ public class ObjectGraph {
private final @With @NonNull RelationType relationType;
private final @With @NonNull ObjectGraph.Object from;
private final @With @NonNull ObjectGraph.Object to;
- private final @With @NonNull String label;
- private final @With @NonNull String label2;
+ private final @With @NonNull String description; // usually the middle
label
+ private final @With @NonNull String nearLabel;
+ private final @With @NonNull String farLabel;
public String fromId() { return from.id(); }
public String toId() { return to.id(); }
- public boolean isAssociation() { return
relationType!=RelationType.INHERITANCE; }
- /**
- * If this is ONE_TO_MANY, decorate the label with enclosing square
brackets.
- */
- public String labelFormatted() {
- return relationType==RelationType.ONE_TO_MANY
- ? String.format("[%s]", label)
- : label;
+ public boolean isAssociation() { return
relationType.isAssociationAny(); }
+ public StringOperator multiplicityNotation() {
+ return relationType.isOneToMany()
+ ? _Strings.asSquareBracketed
+ : StringOperator.identity();
+ }
+ public String descriptionFormatted() {
+ return multiplicityNotation().apply(description);
}
}
- public static enum RelationType {
+ 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 {
@@ -127,6 +142,25 @@ public class ObjectGraph {
renderer.render(sb, this);
return sb.toString();
}
+ public DataSource asDiagramDslSource(final @Nullable ObjectGraph.Renderer
renderer) {
+ var dsl = render(renderer);
+ return dsl==null
+ ? DataSource.empty()
+ : DataSource.ofStringUtf8(dsl);
+ }
+ public void writeDiagramDsl(final @Nullable ObjectGraph.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 @Nullable ObjectGraph.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 objects grouped by package (as list-multimap).
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/_ObjectGraphRelationMerger.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/_ObjectGraphRelationMerger.java
index 04bdeb59ab..a947cd54f6 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/_ObjectGraphRelationMerger.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/objgraph/_ObjectGraphRelationMerger.java
@@ -21,6 +21,7 @@ package
org.apache.causeway.applib.services.metamodel.objgraph;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import org.apache.causeway.commons.internal.collections._Multimaps;
import
org.apache.causeway.commons.internal.collections._Multimaps.ListMultimap;
@@ -61,11 +62,20 @@ class _ObjectGraphRelationMerger implements
ObjectGraph.Transformer {
relations.removeIf(ObjectGraph.Relation::isAssociation);
shared.forEach((key, list) -> {
- val merged = list.stream().reduce((a, b)->new ObjectGraph.Relation(
+ if(list.size()<2) return;
+
+ var mergedDescriptions = list.stream()
+ .map(rel->rel.descriptionFormatted())
+ .collect(Collectors.joining(","));
+
+ var a = list.get(0);
+ var merged = new ObjectGraph.Relation(
ObjectGraph.RelationType.MERGED_ASSOCIATIONS,
- objectById.get(a.fromId()), objectById.get(a.toId()),
- a.labelFormatted() + "," + b.labelFormatted(), ""));
- merged.ifPresent(relations::add);
+ objectById.get(a.fromId()),
+ objectById.get(a.toId()),
+ mergedDescriptions, // already formatted honoring
multiplicity notation
+ "", "");
+ relations.add(merged);
});
}
@@ -92,12 +102,21 @@ class _ObjectGraphRelationMerger implements
ObjectGraph.Transformer {
shared.forEach((key, list) -> {
if(list.size()==2) {
relations.removeAll(list);
- val a = list.get(0);
- val b = list.get(1);
+ var a = list.get(0);
+ var b = list.get(1);
+ // near label receives b's description
+ // far label receives a's description
+ // (formatting has no effect on
RelationType.MERGED_ASSOCIATIONS)
+ var nearLabel = b.descriptionFormatted();
+ var farLabel = a.descriptionFormatted();
+
relations.add(new ObjectGraph.Relation(
ObjectGraph.RelationType.BIDIR_ASSOCIATION,
- objectById.get(a.fromId()), objectById.get(a.toId()),
- a.labelFormatted(), b.labelFormatted()));
+ objectById.get(a.fromId()),
+ objectById.get(a.toId()),
+ "", // middle label is cleared
+ nearLabel,
+ farLabel));
}
});
}
diff --git
a/commons/src/main/java/org/apache/causeway/commons/internal/base/_Strings.java
b/commons/src/main/java/org/apache/causeway/commons/internal/base/_Strings.java
index 071020fdcc..916a9881f5 100644
---
a/commons/src/main/java/org/apache/causeway/commons/internal/base/_Strings.java
+++
b/commons/src/main/java/org/apache/causeway/commons/internal/base/_Strings.java
@@ -421,6 +421,16 @@ public final class _Strings {
: input;
}
+ /**
+ * Returns a {@link StringOperator} that converts given literal into
{@code prefix + literal + suffix} ,
+ * unless the literal is {@code null}, in which case the operator returns
{@code null}.
+ */
+ public static final StringOperator bracketed(final @NonNull String prefix,
final @NonNull String suffix) {
+ return s->s!=null
+ ? prefix + s + suffix
+ : null;
+ }
+
// -- REDUCTION (BINARY OPERATIOR)
/**
@@ -827,14 +837,30 @@ public final class _Strings {
// using naming convention asXxx...
public static final StringOperator asLowerCase = _Strings::lower;
+ public static final StringOperator asUpperCase = _Strings::upper;
+ public static final StringOperator asCapitalized = _Strings::capitalize;
+ public static final StringOperator asDecapitalized =
_Strings::decapitalize;
/**
* Converts given literal into a double-quoted literal, unless the literal
is {@code null},
* in which case {@code null} is returned.
*/
- public static final StringOperator asDoubleQuoted = s->s!=null
- ? "\"" + s + "\""
- : null;
+ public static final StringOperator asDoubleQuoted = bracketed("\"", "\"");
+ /**
+ * Converts given literal into a square-bracketed literal, unless the
literal is {@code null},
+ * in which case {@code null} is returned.
+ */
+ public static final StringOperator asSquareBracketed = bracketed("[", "]");
+ /**
+ * Converts given literal into a parenthesis-bracketed literal, unless the
literal is {@code null},
+ * in which case {@code null} is returned.
+ */
+ public static final StringOperator asParenthesisBracketed = bracketed("(",
")");
+ /**
+ * Converts given literal into a curly-bracketed literal, unless the
literal is {@code null},
+ * in which case {@code null} is returned.
+ */
+ public static final StringOperator asCurlyBracketed = bracketed("{", "}");
public static final StringOperator asLowerDashed = asLowerCase
.compose(s->_Strings.condenseWhitespaces(s, "-"));
@@ -859,10 +885,10 @@ public final class _Strings {
s->_Strings_CamelCase.camelCase(s, firstToken->firstToken);
public static final StringOperator asCamelCaseDecapitalized =
- s->_Strings_CamelCase.camelCase(s,
firstToken->_Strings.decapitalize(firstToken));
+ s->_Strings_CamelCase.camelCase(s, asDecapitalized);
public static final StringOperator asCamelCaseCapitalized =
- s->_Strings_CamelCase.camelCase(s,
firstToken->_Strings.capitalize(firstToken));
+ s->_Strings_CamelCase.camelCase(s, asCapitalized);
public static final StringOperator asPascalCase = asCamelCaseCapitalized;
// synonym
public static final String asFileNameWithExtension(final @NonNull String
fileName, final @NonNull String fileExtension) {
diff --git
a/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java
b/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java
index f11203d917..e3cdb15bb7 100644
--- a/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java
+++ b/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java
@@ -202,6 +202,15 @@ public interface DataSource {
default void pipe(final @NonNull DataSink dataSink, final int bufferSize) {
tryReadAndWrite(dataSink, bufferSize).ifFailureFail();
}
+ /**
+ * Acts as a pipe, reading from this {@link DataSource} and writing to
given {@link DataSink},
+ * using default bufferSize of 16k for the underlying byte data junks.
+ * <p>
+ * Throws if the write failed.
+ */
+ default void pipe(final @NonNull DataSink dataSink) {
+ pipe(dataSink, 16*1024);
+ }
// -- FACTORIES
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/_ObjectGraphFactory.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/_ObjectGraphFactory.java
index 008a1a522c..c4744a05f1 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/_ObjectGraphFactory.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/_ObjectGraphFactory.java
@@ -119,12 +119,13 @@ class _ObjectGraphFactory implements ObjectGraph.Factory {
final ObjectGraph.RelationType relationType,
final String fromId,
final String toId,
- final String label) {
+ final String description) {
val relation = new ObjectGraph.Relation(
relationType,
objectById.get(fromId),
objectById.get(toId),
- label, "");
+ description,
+ "", "");
objectGraph.relations().add(relation);
return relation;
}
@@ -136,12 +137,17 @@ class _ObjectGraphFactory implements ObjectGraph.Factory {
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());
+ return new ObjectGraph.Field(
+ ass.getId(),
+ objectShortName(ass.getElementType()),
+ ass.isOneToManyAssociation(),
+ ass.getStaticDescription());
}
private Set<Relation> createInheritanceRelations() {
@@ -162,7 +168,7 @@ class _ObjectGraphFactory implements ObjectGraph.Factory {
// we found an inheritance relation
val relation = new ObjectGraph.Relation(
ObjectGraph.RelationType.INHERITANCE,
- objectById.get(o1.id()), objectById.get(o2.id()),
"", "");
+ objectById.get(o1.id()), objectById.get(o2.id()),
"", "", "");
inheritanceRelations.add(relation);
}
}
diff --git
a/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/ObjectGraphRendererD3js.java
b/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/ObjectGraphRendererD3js.java
index fbe744fd37..d285f5deae 100644
---
a/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/ObjectGraphRendererD3js.java
+++
b/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/ObjectGraphRendererD3js.java
@@ -102,7 +102,7 @@ public class ObjectGraphRendererD3js implements
ObjectGraph.Renderer {
objGraph.relations().forEach(rel->{
val source = objectLookup.get(rel.from());
val target = objectLookup.get(rel.to());
- d3jsGraph.links.add(new D3jsGraph.Link(source, target,
rel.labelFormatted()));
+ d3jsGraph.links.add(new D3jsGraph.Link(source, target,
rel.multiplicityNotation().apply(rel.description())));
});
renderSvg(sb, d3jsGraph);
diff --git
a/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/plantuml/ObjectGraphRendererPlantuml.java
b/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/plantuml/ObjectGraphRendererPlantuml.java
index 32253c842f..b1c60e0e37 100644
---
a/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/plantuml/ObjectGraphRendererPlantuml.java
+++
b/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/plantuml/ObjectGraphRendererPlantuml.java
@@ -78,9 +78,10 @@ public class ObjectGraphRendererPlantuml implements
ObjectGraph.Renderer {
case ONE_TO_ONE:
case ONE_TO_MANY:
case MERGED_ASSOCIATIONS:
- return String.format("%s -> \"%s\" %s", rel.fromId(),
rel.labelFormatted(), rel.toId());
+ return String.format("%s -> \"%s\" %s", rel.fromId(),
rel.description(), rel.toId());
case BIDIR_ASSOCIATION:
- return String.format("%s \"%s\" -- \"%s\" %s", rel.fromId(),
rel.label(), rel.label2(), rel.toId());
+ return String.format("%s \"%s\" -- \"%s\" %s : %s",
+ rel.fromId(), rel.nearLabel(), rel.farLabel(), rel.toId(),
rel.description());
case INHERITANCE:
return String.format("%s --|> %s", rel.fromId(), rel.toId());
}