+ * Note that this compilation doesn't require instances, so we don't need to run the solver
+ * to compile.
+ *
+ * TODO: this should be refactored to cut down the code re-use.
+ */
+public class AstPlantumlCompiler {
+ private final boolean includeConstraints;
+ private final boolean includeSuperClafersConnections;
+ private final boolean includeSuperClafersComponents;
+ private final int includeLevels;
+
+ private final List clafersBlacklist;
+
+ public AstPlantumlCompiler(AstPlantumlCompilerBuilder builder) {
+ this.includeConstraints = builder.includeConstraints;
+ this.includeSuperClafersConnections = builder.includeSuperClafersConnections;
+ this.includeSuperClafersComponents = builder.includeSuperClafersComponents;
+ this.includeLevels = builder.includeLevels;
+ this.clafersBlacklist = builder.clafersBlacklist;
+ }
+
+ /**
+ * Check that a clafer name matches an entry in the blacklist
+ *
+ * @param name
+ * @return whether name should be blacklisted
+ */
+ boolean checkBlacklist(String name) {
+ for (String regex : this.clafersBlacklist) {
+ Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+ Matcher matcher = pattern.matcher(name);
+ boolean matchFound = matcher.find();
+ if (matchFound) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check that a clafer is allowed
+ *
+ * Note that we ignore all names beginning with # because they are
+ * generated by the clafer compiler and implicit to the user's code.
+ *
+ * @param name
+ * @return whether name should be compiled
+ */
+ private boolean checkClaferName(String name) {
+ return !checkBlacklist(name) && !name.startsWith("#");
+ }
+
+ /**
+ * collect all concrete clafers
+ *
+ * @param concreteClafers concreteClafers held in a claferModel
+ * @return ArrayList of all nested clafers (abstract included)
+ */
+ private ArrayList getConcreteObjects(List concreteClafers, int level) {
+ ArrayList objs = new ArrayList();
+
+ for (AstConcreteClafer ast : concreteClafers) {
+ if (ast.getRef() != null) continue;
+
+ // build the property groups
+ ArrayList pgs = new ArrayList();
+
+ ArrayList constrs = new ArrayList();
+ for (AstConstraint constr : ast.getConstraints()) {
+ constrs.add(new PlantumlProperty(constr.toString()));
+ }
+ if (constrs.size() > 0 && this.includeConstraints) {
+ pgs.add(new PlantumlPropertyGroup("Constraints", constrs.toArray(new PlantumlProperty[0])));
+ }
+
+ // Display inheritance as a style notation
+ // NOTE: this could be made optional
+ AstClafer superClafer = ast.getSuperClafer();
+ String scName = "";
+ if (superClafer != null && !PlantumlCompilerUtils.getPropertyId(superClafer.getName()).startsWith("#") && this.includeSuperClafersComponents) {
+ scName = PlantumlCompilerUtils.getPropertyId(superClafer.getName());
+ }
+
+ // create an object and add it
+ PlantumlObject obj = new PlantumlObject(
+ PlantumlCompilerUtils.getPropertyId(ast.getName()),
+ PlantumlCompilerUtils.getPropertyAlias(ast.getName()),
+ scName,
+ pgs.toArray(new PlantumlPropertyGroup[0])
+ );
+
+ if (checkClaferName(obj.getName())) objs.add(obj);
+
+ // add all of its children
+ // TODO: check for collisions?
+ if (level < this.includeLevels) objs.addAll(getConcreteObjects(ast.getChildren(), level + 1));
+ }
+ return objs;
+ }
+
+ /**
+ * collect all abstract clafers (give them an abstract attribute)
+ *
+ * @param abstractClafers abstractClafers held in a claferModel
+ * @return ArrayList of all nested clafers (concrete included)
+ */
+ private ArrayList getAbstractObjects(List abstractClafers, int level) {
+ ArrayList objs = new ArrayList();
+
+ for (AstAbstractClafer ast : abstractClafers) {
+ if (ast.getRef() != null) {
+ continue;
+ }
+
+ // build out the constraints and attributes property groups
+ ArrayList pgs = new ArrayList();
+
+ ArrayList constrs = new ArrayList();
+ for (AstConstraint constr : ast.getConstraints()) {
+ constrs.add(new PlantumlProperty(constr.toString()));
+ }
+
+ ArrayList refs = new ArrayList();
+ for (AstConcreteClafer clafer : ast.getChildren()) {
+ AstRef ref = clafer.getRef();
+ if (ref != null) {
+ refs.add(new PlantumlProperty(ref.toString()));
+ }
+ }
+
+ if (refs.size() > 0) {
+ pgs.add(new PlantumlPropertyGroup("Attributes", refs.toArray(new PlantumlProperty[0])));
+ }
+
+ if (constrs.size() > 0 && this.includeConstraints) {
+ pgs.add(new PlantumlPropertyGroup("Constraints", constrs.toArray(new PlantumlProperty[0])));
+ }
+
+ // create an object and add it
+ PlantumlObject obj = new PlantumlObject(
+ PlantumlCompilerUtils.getPropertyId(ast.getName()),
+ PlantumlCompilerUtils.getPropertyAlias(ast.getName()),
+ null,
+ pgs.toArray(new PlantumlPropertyGroup[0])
+ );
+
+ if (checkClaferName(obj.getName())) objs.add(obj);
+
+ // add all of its children
+ // TODO: check for collisions?
+ if (level < this.includeLevels) objs.addAll(getAbstractObjects(ast.getAbstractChildren(), level + 1));
+ if (level < this.includeLevels) objs.addAll(getConcreteObjects(ast.getChildren(), level + 1));
+ }
+ return objs;
+ }
+
+ /**
+ * top-level object collector
+ *
+ * @param model the root clafer model
+ * @return ArrayList of all clafers (abstract and concrete) suitable for PlantUML objects
+ */
+ private ArrayList getObjects(AstModel model) {
+ ArrayList objs = getAbstractObjects(model.getAbstracts(), 0);
+ objs.addAll(getConcreteObjects(model.getChildren(), 0));
+ return objs;
+ }
+
+ private char getEndArrowhead(boolean hasGroupCard, Card card, Card groupCard) {
+ char toConn = '*';
+ if (hasGroupCard) toConn = '-';
+ else {
+ // No Alternative/OR
+ if (card.toString().equals("0..1")) toConn = 'o';
+ else if (card.toString().equals("1")) toConn = '*';
+ else {
+ if (card.toString().startsWith("0")) toConn = 'o';
+ }
+ }
+ return toConn;
+ }
+
+ private char getStartArrowhead(boolean hasGroupCard, Card groupCard) {
+ char fromConn = '-';
+ if (hasGroupCard) {
+ if (groupCard.toString().equals("1")) {
+ // This is an OR (exactly one)
+ fromConn = '+';
+ } else if (groupCard.toString().equals("1..*")) {
+ // This is an Alternative (1 or more)
+ fromConn = ')';
+ }
+ }
+ return fromConn;
+ }
+
+ private String getLabel(boolean hasGroupCard, Card card) {
+ String label = "";
+ if (!hasGroupCard) {
+ // No Alternative/OR
+ if (!card.toString().equals("0..1") && !card.toString().equals("1")) {
+ label = card.toString();
+ }
+ }
+ return label;
+ }
+
+ private ArrayList getConcreteConnections(List concreteClafers, int level) {
+ ArrayList connections = new ArrayList();
+
+ for (AstConcreteClafer ast : concreteClafers) {
+ if (ast.getRef() != null) {
+ continue;
+ }
+ String fromObj = PlantumlCompilerUtils.getPropertyId(ast.getParent().getName());
+ String toObj = PlantumlCompilerUtils.getPropertyId(ast.getName());
+
+ Card card = ast.getCard();
+ boolean hasGroupCard = ast.getParent().hasGroupCard();
+
+ String label = getLabel(hasGroupCard, card);
+ char fromConn = getStartArrowhead(hasGroupCard, ast.getParent().getGroupCard());
+ char toConn = getEndArrowhead(hasGroupCard, card, ast.getParent().getGroupCard());
+
+ if (checkClaferName(fromObj) && checkClaferName(toObj)) {
+ connections.add(
+ new PlantumlConnection(
+ fromObj,
+ toObj,
+ fromConn,
+ toConn,
+ label
+ )
+ );
+ }
+
+ if (this.includeSuperClafersConnections) {
+ AstClafer superClafer = ast.getSuperClafer();
+ if (superClafer != null) {
+ String scName = PlantumlCompilerUtils.getPropertyId(superClafer.getName());
+ if (checkClaferName(scName)) {
+ fromObj = toObj;
+ toObj = scName;
+ connections.add(
+ new PlantumlConnection(
+ fromObj,
+ toObj,
+ '.',
+ '>',
+ "",
+ '.'
+ )
+ );
+ }
+ }
+ }
+
+ if (level < this.includeLevels) connections.addAll(getConcreteConnections(ast.getChildren(), level + 1));
+ }
+
+ return connections;
+ }
+
+ private ArrayList getAbstractConnections(List abstractClafers, int level) {
+ ArrayList connections = new ArrayList();
+
+ for (AstAbstractClafer ast : abstractClafers) {
+ if (ast.getRef() != null) continue;
+
+ // collect the connection labels
+ String fromObj = PlantumlCompilerUtils.getPropertyId(ast.getParent().getName());
+ String toObj = PlantumlCompilerUtils.getPropertyId(ast.getName());
+ String label = "";
+ char toConn = '*';
+ char fromConn = getStartArrowhead(ast.getParent().hasGroupCard(), ast.getParent().getGroupCard());
+
+ if (checkClaferName(fromObj) && checkClaferName(toObj)) {
+ connections.add(
+ new PlantumlConnection(
+ fromObj,
+ toObj,
+ fromConn,
+ toConn,
+ label
+ )
+ );
+ }
+
+ if (this.includeSuperClafersConnections) {
+ AstClafer superClafer = ast.getSuperClafer();
+ if (superClafer != null) {
+ String scName = PlantumlCompilerUtils.getPropertyId(superClafer.getName());
+ if (!scName.startsWith("#") && !checkBlacklist(scName)) {
+ fromObj = toObj;
+ toObj = scName;
+ connections.add(
+ new PlantumlConnection(
+ fromObj,
+ toObj,
+ '.',
+ '>',
+ "",
+ '.'
+ )
+ );
+ }
+ }
+ }
+
+ if (level < this.includeLevels)
+ connections.addAll(getAbstractConnections(ast.getAbstractChildren(), level + 1));
+ if (level < this.includeLevels) connections.addAll(getConcreteConnections(ast.getChildren(), level + 1));
+ }
+
+ return connections;
+ }
+
+ private ArrayList getConnections(AstModel model) {
+ ArrayList connections = getAbstractConnections(model.getAbstracts(), 0);
+ connections.addAll(getConcreteConnections(model.getChildren(), 0));
+ return connections;
+ }
+
+ public PlantumlProgram compile(AstModel model) {
+ ArrayList objs = getObjects(model);
+ ArrayList conns = getConnections(model);
+
+ return new PlantumlProgram(
+ objs.toArray(new PlantumlObject[0]), conns.toArray(new PlantumlConnection[0])
+ );
+ }
+
+
+ /**
+ * builder class to configure the Compiler
+ * we anticipate a lot of options here, so we use a builder
+ */
+ public static class AstPlantumlCompilerBuilder {
+
+ private boolean includeConstraints;
+ private boolean includeSuperClafersConnections;
+ private boolean includeSuperClafersComponents;
+ private int includeLevels;
+
+ private List clafersBlacklist;
+
+ public AstPlantumlCompilerBuilder() {
+ this.includeConstraints = true;
+ this.includeSuperClafersConnections = true;
+ this.includeSuperClafersComponents = true;
+ this.includeLevels = Integer.MAX_VALUE;
+ this.clafersBlacklist = new ArrayList();
+ }
+
+ public AstPlantumlCompiler build() {
+ return new AstPlantumlCompiler(this);
+ }
+
+ public AstPlantumlCompilerBuilder setIncludeConstraints(boolean includeConstraints) {
+ this.includeConstraints = includeConstraints;
+ return this;
+ }
+
+ public AstPlantumlCompilerBuilder setIncludeSuperClafersConnections(boolean includeSuperClafers) {
+ this.includeSuperClafersConnections = includeSuperClafers;
+ return this;
+ }
+
+ public AstPlantumlCompilerBuilder setIncludeSuperClafersComponents(boolean includeSuperClafers) {
+ this.includeSuperClafersComponents = includeSuperClafers;
+ return this;
+ }
+
+ public AstPlantumlCompilerBuilder setLevels(int levels) {
+ this.includeLevels = levels;
+ return this;
+ }
+
+ public AstPlantumlCompilerBuilder setClafersBlacklist(List bl) {
+ this.clafersBlacklist = bl;
+ return this;
+ }
+
+ /**
+ * read builder from an input toml file (null returns defaults)
+ *
+ * @param tomlFile config file
+ * @return a compiler object
+ * @throws IOException
+ */
+ static public AstPlantumlCompiler buildFromToml(File tomlFile) throws IOException {
+ if (tomlFile == null) {
+ return new AstPlantumlCompiler(new AstPlantumlCompilerBuilder());
+ } else {
+ // TODO: Toml4J seems to do better error checking than this
+ Path source = Paths.get(tomlFile.toURI());
+ TomlParseResult result = Toml.parse(source);
+ result.errors().forEach(error -> System.err.println(error.toString()));
+
+ AstPlantumlCompilerBuilder build = new AstPlantumlCompilerBuilder();
+
+ String field = "include.super_clafers_connections";
+ if (result.contains(field)) build.setIncludeSuperClafersConnections(Boolean.TRUE.equals(result.getBoolean(field)));
+
+ field = "include.super_clafers_components";
+ if (result.contains(field)) build.setIncludeSuperClafersComponents(Boolean.TRUE.equals(result.getBoolean(field)));
+
+ field = "include.constraints";
+ if (result.contains(field)) build.setIncludeConstraints(Boolean.TRUE.equals(result.getBoolean(field)));
+
+ field = "include.levels";
+ if (result.contains(field)) build.setLevels(Objects.requireNonNull(result.getLong(field)).intValue());
+
+ field = "blacklist.clafers";
+ if (result.contains(field)) {
+ List blacklist = new ArrayList<>();
+ for (Object o : result.getArrayOrEmpty(field)
+ .toList()) {
+ String s = Objects.toString(o, null);
+ blacklist.add(s);
+ }
+ build.setClafersBlacklist(blacklist);
+ }
+
+ return build.build();
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/plantuml/compiler/PlantumlCompilerUtils.java b/src/main/java/org/plantuml/compiler/PlantumlCompilerUtils.java
new file mode 100644
index 00000000..7410cd7c
--- /dev/null
+++ b/src/main/java/org/plantuml/compiler/PlantumlCompilerUtils.java
@@ -0,0 +1,31 @@
+package org.plantuml.compiler;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PlantumlCompilerUtils {
+ public static String getPropertyId(String name) {
+ Pattern pattern = Pattern.compile("(c[0-9]+_).*", Pattern.CASE_INSENSITIVE);
+ Matcher matcher = pattern.matcher(name);
+ boolean matchFound = matcher.find();
+ //matcher.group()
+ if (matchFound) {
+ String idxS = matcher.group(1);
+ int idx = Integer.parseInt(idxS.substring(1, idxS.length() - 1));
+ return name.substring(matcher.group(1).length()) + "_" + String.valueOf(idx);
+ } else {
+ return name;
+ }
+ }
+
+ public static String getPropertyAlias(String name) {
+ Pattern pattern = Pattern.compile("(c[0-9]+_).*", Pattern.CASE_INSENSITIVE);
+ Matcher matcher = pattern.matcher(name);
+ boolean matchFound = matcher.find();
+ if (matchFound) {
+ return name.substring(matcher.group(1).length());
+ } else {
+ return name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java
new file mode 100644
index 00000000..0f964251
--- /dev/null
+++ b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java
@@ -0,0 +1,120 @@
+package org.plantuml.pprinter;
+
+import org.plantuml.ast.*;
+import org.tomlj.Toml;
+import org.tomlj.TomlParseResult;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * PlantUML -> Text
+ *
+ * Visits the PlantUML AST and generates text output to an Appendable stream
+ */
+public class PlantumlPrinter implements PlantumlExprVisitor {
+ private final String indentBase;
+ private final Appendable out;
+ private String header;
+
+ /**
+ * Initialize a printer
+ * @param out appendable stream
+ * @param tomlFile config file
+ * @throws IOException
+ */
+ public PlantumlPrinter(Appendable out, File tomlFile) throws IOException {
+ this.out = out;
+ this.indentBase = " ";
+ this.header = "";
+ if (tomlFile != null) {
+ // NOTE: silly that we are redoing what is done in AstPlantumlCompilerBuilder to get the header
+ Path source = Paths.get(tomlFile.toURI());
+ TomlParseResult result = Toml.parse(source);
+ result.errors().forEach(error -> System.err.println(error.toString()));
+ String field = "include.header";
+ if (result.contains(field)) {
+ this.header = result.getString(field);
+ }
+ }
+ }
+
+ // implement the visitor
+ @Override
+ public Void visit(PlantumlProgram ast, String indent) throws IOException {
+ this.out.append(indent).append("@startuml").append("\n").append(this.header).append("\n");
+ for (PlantumlObject obj: ast.getObjects()){
+ obj.accept(this, indent + indentBase);
+ }
+ this.out.append('\n');
+ for (PlantumlConnection conn: ast.getConnections()){
+ conn.accept(this, indent + indentBase);
+ }
+ this.out.append(indent).append("@enduml").append("\n");
+ return null;
+ }
+
+ @Override
+ public Void visit(PlantumlObject plantumlObject, String s) throws IOException {
+ this.out.append(s).append("object ");
+
+ String alias = plantumlObject.getAlias();
+ String parent = plantumlObject.getParent();
+
+ if (alias != null) {
+ this.out.append('"').append(alias).append('"').append(" as ");
+ }
+
+ this.out.append(plantumlObject.getName());
+
+ if (parent != null ) {
+ if (!parent.isEmpty()) {
+ this.out.append("<<").append(parent).append(">>");
+ }
+ }
+
+ if (plantumlObject.getPropertyGroups().length > 0) {
+ this.out.append(" {\n");
+ for (PlantumlPropertyGroup grp: plantumlObject.getPropertyGroups()){
+ grp.accept(this, s + indentBase);
+ }
+ this.out.append(s).append("}\n");
+ } else {
+ this.out.append("\n");
+ }
+ return null;
+ }
+
+ @Override
+ public Void visit(PlantumlPropertyGroup plantumlPropertyGroup, String s) throws IOException {
+ this.out.append(s).append(".. ").append(plantumlPropertyGroup.getName()).append(" ..").append("\n");
+ for (PlantumlProperty prop: plantumlPropertyGroup.getProperties()){
+ prop.accept(this, s);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visit(PlantumlProperty plantumlProperty, String s) throws IOException {
+ this.out.append(s).append("* ").append(plantumlProperty.getProp()).append('\n');
+ return null;
+ }
+
+ @Override
+ public Void visit(PlantumlConnection plantumlConnection, String s) throws IOException {
+ this.out.append(s)
+ .append(plantumlConnection.getFromObj())
+ .append(" ").append(plantumlConnection.getFromConn()).append(plantumlConnection.getLineChar()).append(plantumlConnection.getToConn())
+ .append(" ")
+ .append(plantumlConnection.getToObj());
+ if (plantumlConnection.getLabel().length() > 0){
+ this.out.append(" ")
+ .append(": ")
+ .append(plantumlConnection.getLabel());
+ }
+ this.out.append('\n');
+ return null;
+ }
+}
diff --git a/src/main/java/org/sysml/ast/SysmlAttribute.java b/src/main/java/org/sysml/ast/SysmlAttribute.java
new file mode 100644
index 00000000..3d6a7f0f
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlAttribute.java
@@ -0,0 +1,27 @@
+package org.sysml.ast;
+
+import java.io.IOException;
+
+public class SysmlAttribute implements SysmlExpr, SysmlId {
+ private String name;
+
+ private Object ref;
+
+ public SysmlAttribute(String name, Object ref) {
+ this.name = name;
+ this.ref = ref;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public String getRef() {
+ return ref.toString();
+ }
+ @Override
+ public B accept(SysmlExprVisitor visitor, A a) throws IOException {
+ return visitor.visit(this, a);
+ }
+}
diff --git a/src/main/java/org/sysml/ast/SysmlBlockDefElement.java b/src/main/java/org/sysml/ast/SysmlBlockDefElement.java
new file mode 100644
index 00000000..6707f189
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlBlockDefElement.java
@@ -0,0 +1,4 @@
+package org.sysml.ast;
+
+public interface SysmlBlockDefElement extends SysmlExpr, SysmlId {
+}
diff --git a/src/main/java/org/sysml/ast/SysmlBlockVisibility.java b/src/main/java/org/sysml/ast/SysmlBlockVisibility.java
new file mode 100644
index 00000000..bacb52ed
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlBlockVisibility.java
@@ -0,0 +1,14 @@
+package org.sysml.ast;
+
+/**
+ * Sysml Block Visibility
+ * ::= | ‘#’ | ‘~’
+ */
+public class SysmlBlockVisibility {
+ public final SysmlVisibilityOption option;
+
+ public SysmlBlockVisibility(SysmlVisibilityOption visOpt) {
+ this.option = visOpt;
+ }
+
+}
diff --git a/src/main/java/org/sysml/ast/SysmlExpr.java b/src/main/java/org/sysml/ast/SysmlExpr.java
new file mode 100644
index 00000000..2e64882c
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlExpr.java
@@ -0,0 +1,16 @@
+package org.sysml.ast;
+
+import java.io.IOException;
+
+public interface SysmlExpr {
+ /**
+ * Dynamic dispatch on the visitor.
+ *
+ * @param the parameter type
+ * @param the return type
+ * @param visitor the visitor
+ * @param a the parameter
+ * @return the return value
+ */
+ B accept(SysmlExprVisitor visitor, A a) throws IOException;
+}
diff --git a/src/main/java/org/sysml/ast/SysmlExprVisitor.java b/src/main/java/org/sysml/ast/SysmlExprVisitor.java
new file mode 100644
index 00000000..7cb6f748
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlExprVisitor.java
@@ -0,0 +1,13 @@
+package org.sysml.ast;
+
+import java.io.IOException;
+
+public interface SysmlExprVisitor {
+ B visit(SysmlPackage ast, A a) throws IOException;
+
+ B visit(SysmlProperty ast, A a) throws IOException;
+
+ B visit(SysmlAttribute ast, A a) throws IOException;
+
+ B visit(SysmlPropertyDef sysmlPropertyDef, A a) throws IOException;
+}
diff --git a/src/main/java/org/sysml/ast/SysmlId.java b/src/main/java/org/sysml/ast/SysmlId.java
new file mode 100644
index 00000000..816d7dad
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlId.java
@@ -0,0 +1,10 @@
+package org.sysml.ast;
+
+public interface SysmlId {
+ /**
+ * SysML
+ *
+ * @return the name of the identifier
+ */
+ String getName();
+}
diff --git a/src/main/java/org/sysml/ast/SysmlPackage.java b/src/main/java/org/sysml/ast/SysmlPackage.java
new file mode 100644
index 00000000..d3e478ba
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlPackage.java
@@ -0,0 +1,31 @@
+package org.sysml.ast;
+
+
+
+import java.io.IOException;
+
+/**
+ * TODO: build out the DiagramElement taxonomy better
+ */
+public class SysmlPackage implements SysmlBlockDefElement {
+ private final SysmlBlockDefElement[] elements;
+ private final String name;
+
+ public SysmlPackage(String name, SysmlBlockDefElement[] elements){
+ this.elements = elements;
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public SysmlBlockDefElement[] getElements(){
+ return elements;
+ }
+
+ @Override
+ public B accept(SysmlExprVisitor visitor, A a) throws IOException {
+ return visitor.visit(this, a);
+ }
+}
diff --git a/src/main/java/org/sysml/ast/SysmlProperty.java b/src/main/java/org/sysml/ast/SysmlProperty.java
new file mode 100644
index 00000000..5f8f9747
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlProperty.java
@@ -0,0 +1,84 @@
+package org.sysml.ast;
+
+import org.clafer.common.Check;
+
+import java.io.IOException;
+
+/**
+ * SysML Property
+ *
+ * ::= [] [‘/’] []
+ *
+ * TODO: do the declaration
+ */
+public class SysmlProperty implements SysmlBlockDefElement {
+
+ private final String name;
+
+ private final SysmlPropertyType prop;
+
+ private final SysmlBlockDefElement[] elements;
+
+ private final SysmlAttribute[] annotations;
+ private final String[] superTypes;
+
+ private int multiplicity;
+
+
+
+ public SysmlProperty(
+ SysmlBlockVisibility blockVis,
+ SysmlPropertyType propType,
+ String name,
+ SysmlBlockDefElement[] elements,
+ SysmlAttribute[] annotations,
+ String[] superTypes,
+ int multiplicity
+ ){
+ this.name = Check.notNull(name);
+ this.prop = propType;
+ this.elements = elements;
+ this.superTypes = superTypes;
+ this.multiplicity = multiplicity;
+ this.annotations = annotations;
+ }
+
+ public SysmlProperty(SysmlBlockVisibility blockVis, SysmlPropertyType propType, String name){
+ this.name = Check.notNull(name);
+ this.prop = propType;
+ this.elements = new SysmlBlockDefElement[0];
+ this.superTypes = new String[0];
+ this.annotations = new SysmlAttribute[0];
+ this.multiplicity = 1;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public SysmlPropertyType getPropertyType(){
+ return prop;
+ }
+
+ public SysmlBlockDefElement[] getElements() {
+ return elements;
+ }
+
+ public String[] getSupers(){
+ return superTypes;
+ }
+
+ public int getMultiplicity(){
+ return multiplicity;
+ }
+
+ public SysmlAttribute[] getAnnotations(){
+ return annotations;
+ }
+
+ @Override
+ public B accept(SysmlExprVisitor visitor, A a) throws IOException {
+ return visitor.visit(this, a);
+ }
+}
diff --git a/src/main/java/org/sysml/ast/SysmlPropertyDef.java b/src/main/java/org/sysml/ast/SysmlPropertyDef.java
new file mode 100644
index 00000000..dd61de05
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlPropertyDef.java
@@ -0,0 +1,65 @@
+package org.sysml.ast;
+
+import org.clafer.common.Check;
+
+import java.io.IOException;
+
+public class SysmlPropertyDef implements SysmlBlockDefElement {
+ private final String name;
+
+ private final SysmlPropertyType prop;
+
+ private final SysmlBlockDefElement[] elements;
+
+ private final SysmlAttribute[] annotations;
+ private final String[] superTypes;
+
+ public SysmlPropertyDef(
+ SysmlBlockVisibility blockVis,
+ SysmlPropertyType propType,
+ String name,
+ SysmlBlockDefElement[] elements,
+ SysmlAttribute[] annotations,
+ String[] superTypes
+ ){
+ this.name = Check.notNull(name);
+ this.prop = propType;
+ this.elements = elements;
+ this.superTypes = superTypes;
+ this.annotations = annotations;
+ }
+
+ public SysmlPropertyDef(SysmlBlockVisibility blockVis, SysmlPropertyType propType, String name){
+ this.name = Check.notNull(name);
+ this.prop = propType;
+ this.elements = new SysmlBlockDefElement[0];
+ this.superTypes = new String[0];
+ this.annotations = new SysmlAttribute[0];
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public SysmlPropertyType getPropertyType(){
+ return prop;
+ }
+
+ public SysmlBlockDefElement[] getElements() {
+ return elements;
+ }
+
+ public String[] getSupers(){
+ return superTypes;
+ }
+
+ public SysmlAttribute[] getAnnotations(){
+ return annotations;
+ }
+
+ @Override
+ public B accept(SysmlExprVisitor visitor, A a) throws IOException {
+ return visitor.visit(this, a);
+ }
+}
diff --git a/src/main/java/org/sysml/ast/SysmlPropertyType.java b/src/main/java/org/sysml/ast/SysmlPropertyType.java
new file mode 100644
index 00000000..837ef6b0
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlPropertyType.java
@@ -0,0 +1,19 @@
+package org.sysml.ast;
+
+/**
+ * Sysml Property Keyword
+ * ::= ‘part’ | ‘reference’ | ‘value’ |
+ *
+ * TODO: accoutn for special cases
+ */
+public class SysmlPropertyType implements SysmlId {
+ private final String name;
+ public SysmlPropertyType(String name){
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/java/org/sysml/ast/SysmlVisibilityOption.java b/src/main/java/org/sysml/ast/SysmlVisibilityOption.java
new file mode 100644
index 00000000..524c3e68
--- /dev/null
+++ b/src/main/java/org/sysml/ast/SysmlVisibilityOption.java
@@ -0,0 +1,8 @@
+package org.sysml.ast;
+
+public enum SysmlVisibilityOption {
+ PLUS, // namespace-visibility
+ MINUS, // namespace-visibility
+ NUMBER,
+ TILDE
+}
diff --git a/src/main/java/org/sysml/compiler/AstSysmlCompiler.java b/src/main/java/org/sysml/compiler/AstSysmlCompiler.java
new file mode 100644
index 00000000..3ecfdb39
--- /dev/null
+++ b/src/main/java/org/sysml/compiler/AstSysmlCompiler.java
@@ -0,0 +1,64 @@
+package org.sysml.compiler;
+
+import org.clafer.ast.AstAbstractClafer;
+import org.clafer.ast.AstClafer;
+import org.clafer.ast.AstModel;
+import org.clafer.ast.AstRef;
+import org.clafer.instance.InstanceClafer;
+import org.sysml.ast.*;
+
+import java.util.*;
+
+public class AstSysmlCompiler {
+ private Map typeMap;
+
+ public AstSysmlCompiler(){
+ this.typeMap = new HashMap<>();
+ this.typeMap.put("int", "Integer");
+ this.typeMap.put("real", "Real");
+ this.typeMap.put("double", "Real");
+ this.typeMap.put("string", "String");
+ }
+
+ public SysmlPropertyDef[] compile(AstModel model, AstModel topLevelModel) {
+ ArrayList propDefs = new ArrayList();
+
+ for (AstAbstractClafer child: model.getAbstractRoot().getAbstractChildren()){
+ String name = SysmlCompilerUtils.getPropertyId(child.getName());
+
+ ArrayList attrs = new ArrayList();
+ for (AstClafer sub: child.getChildren()) {
+ Object sref = sub.getRef();
+ if (sref instanceof AstRef){
+ AstRef ref = (AstRef) sref;
+ Object tgt = typeMap.get(ref.getTargetType().toString());
+ String propName = SysmlCompilerUtils.getPropertyId(ref.getSourceType().getName());
+ attrs.add(new SysmlAttribute(propName, tgt));
+ }
+ }
+
+ String[] superClafers = SysmlCompilerUtils.getSuperClafers(child);
+ List hierarchy = Arrays.asList(superClafers);
+ if (hierarchy.size() < 3){
+ continue;
+ }
+
+ String[] superTypes = new String[0];
+ if (hierarchy.contains("SysmlProperty")) {
+ superTypes = Arrays.copyOfRange(superClafers, 0, hierarchy.indexOf("SysmlProperty")-1);
+ }
+
+ propDefs.add(new SysmlPropertyDef(
+ new SysmlBlockVisibility(SysmlVisibilityOption.PLUS),
+ new SysmlPropertyType("part"),
+ name,
+ new SysmlBlockDefElement[0],
+ attrs.toArray(new SysmlAttribute[attrs.size()]),
+ superTypes
+ ));
+
+ }
+
+ return propDefs.toArray(new SysmlPropertyDef[propDefs.size()]);
+ }
+}
diff --git a/src/main/java/org/sysml/compiler/InstanceSysmlCompiler.java b/src/main/java/org/sysml/compiler/InstanceSysmlCompiler.java
new file mode 100644
index 00000000..4fdfdf61
--- /dev/null
+++ b/src/main/java/org/sysml/compiler/InstanceSysmlCompiler.java
@@ -0,0 +1,103 @@
+package org.sysml.compiler;
+
+import org.clafer.ast.AstClafer;
+import org.clafer.ast.AstRef;
+import org.clafer.instance.InstanceClafer;
+import org.sysml.ast.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class InstanceSysmlCompiler {
+
+ // it will use the FIRST clafer named SysmlProperty
+ public final String baseSysmlClafer = "SysmlProperty";
+
+ private ArrayList processedClafers;
+
+ public InstanceSysmlCompiler(){
+ this.processedClafers = new ArrayList();
+ }
+
+ int getMultiplicity(InstanceClafer model, AstClafer clafer){
+ int count = 0;
+ if (model.getType().getName().equals(clafer.getName())){
+ count++;
+ }
+ for (InstanceClafer child : model.getChildren()) {
+ count += getMultiplicity(child, clafer);
+ }
+ return count;
+ }
+
+ public SysmlProperty compile(InstanceClafer model, InstanceClafer topLevelModel) {
+ // collect the identifier
+ String propertyName =SysmlCompilerUtils.getPropertyId(model.getType().getName());
+
+ // get its supers
+ String[] superClafers = SysmlCompilerUtils.getSuperClafers(model.getType());
+ List hierarchy = Arrays.asList(superClafers);
+
+ // process the children
+ ArrayList children = new ArrayList();
+ for (InstanceClafer child : model.getChildren()) {
+ if (!processedClafers.contains(child.getType().getName())) {
+ Object _cchild = compile(child, topLevelModel);
+ if (_cchild != null) {
+ SysmlProperty cchild = (SysmlProperty) _cchild;
+ if (!cchild.getPropertyType().getName().equals("unk")) {
+ children.add(cchild);
+ } else {
+ SysmlBlockDefElement[] schildren = cchild.getElements();
+ children.addAll(new ArrayList(Arrays.asList(schildren)));
+ }
+ }
+ }
+ }
+
+ // get the property name
+ String propName = "";
+ String[] superTypes = new String[0];
+ if (hierarchy.contains(baseSysmlClafer)) {
+ propName = ((String) hierarchy.get(hierarchy.indexOf(baseSysmlClafer) - 1)).toLowerCase();
+ superTypes = Arrays.copyOfRange(superClafers, 0, hierarchy.indexOf(baseSysmlClafer)-1);
+ } else {
+ propName = "unk";
+ }
+
+ // get the clafer multiplicity and mark as processed
+ int multiplicity = getMultiplicity(topLevelModel, model.getType());
+ processedClafers.add(model.getType().getName());
+
+ // collect the annotations
+ ArrayList annots = new ArrayList();
+ for (InstanceClafer child : model.getChildren()) {
+ Object ref = child.getType().getRef();
+ if (ref != null) {
+ AstRef aref = (AstRef) ref;
+ String aname = SysmlCompilerUtils.getPropertyId(aref.getSourceType().getName());
+ Object refv = child.getRef();
+ annots.add(new SysmlAttribute(aname, refv));
+ }
+ }
+
+ // build a property object
+ if (propName.equals("unk") && children.size() == 0){
+ return null;
+ } else {
+ return new SysmlProperty(
+ new SysmlBlockVisibility(SysmlVisibilityOption.PLUS),
+ new SysmlPropertyType(propName),
+ propertyName,
+ children.toArray(new SysmlBlockDefElement[children.size()]),
+ annots.toArray(new SysmlAttribute[annots.size()]),
+ superTypes,
+ multiplicity
+ );
+ }
+ }
+
+}
diff --git a/src/main/java/org/sysml/compiler/SysmlCompilerUtils.java b/src/main/java/org/sysml/compiler/SysmlCompilerUtils.java
new file mode 100644
index 00000000..40afe812
--- /dev/null
+++ b/src/main/java/org/sysml/compiler/SysmlCompilerUtils.java
@@ -0,0 +1,34 @@
+package org.sysml.compiler;
+
+import org.clafer.ast.AstClafer;
+
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SysmlCompilerUtils {
+ public static String getPropertyId(String name){
+ Pattern pattern = Pattern.compile("(c[0-9]+_).*", Pattern.CASE_INSENSITIVE);
+ Matcher matcher = pattern.matcher(name);
+ boolean matchFound = matcher.find();
+ //matcher.group()
+ if (matchFound){
+ return name.substring(matcher.group(1).length());
+ } else {
+ return name;
+ }
+ }
+
+ public static String[] getSuperClafers(AstClafer model){
+ Object spr = model.getSuperClafer();
+ if (spr == null){
+ return new String[0];
+ } else {
+ String[] rem = getSuperClafers((AstClafer) spr);
+ String[] sprs = Arrays.copyOf(rem, rem.length + 1);
+ sprs[0] = SysmlCompilerUtils.getPropertyId(((AstClafer) spr).getName());
+ System.arraycopy(rem, 0, sprs, 1, rem.length);
+ return sprs;
+ }
+ }
+}
diff --git a/src/main/java/org/sysml/pprinter/SysmlPrinter.java b/src/main/java/org/sysml/pprinter/SysmlPrinter.java
new file mode 100644
index 00000000..459419b7
--- /dev/null
+++ b/src/main/java/org/sysml/pprinter/SysmlPrinter.java
@@ -0,0 +1,102 @@
+package org.sysml.pprinter;
+
+import org.sysml.ast.*;
+
+import java.io.IOException;
+import java.lang.Void;
+
+public class SysmlPrinter implements SysmlExprVisitor {
+
+ private final String indent_base;
+ private final Appendable out;
+
+ public SysmlPrinter(Appendable out) {
+ this.out = out;
+ this.indent_base = " ";
+ }
+
+
+ public void print(String indent, SysmlPackage spackage, Appendable out) throws IOException {
+ out.append(indent).append("package ").append(spackage.getName()).append(" {").append("\n");
+ for (SysmlBlockDefElement elem : spackage.getElements()) {
+ print(indent + indent_base, elem, out);
+ out.append('\n');
+ }
+ out.append(indent).append("}");
+ }
+
+ public void print(String indent, SysmlBlockDefElement sprop, Appendable out) throws IOException {
+ }
+
+ public Void visit(SysmlAttribute ast, String indent) throws IOException {
+ this.out.append(indent).append(":>> ").append(ast.getName()).append(" = ").append(ast.getRef()).append(";\n");
+ return null;
+ }
+
+ @Override
+ public Void visit(SysmlProperty ast, String indent) throws IOException {
+ this.out
+ .append(indent)
+ .append(ast.getPropertyType().getName())
+ .append(" ")
+ .append(ast.getName())
+ ;
+ if (ast.getMultiplicity() > 1) {
+ this.out.append("[").append(String.valueOf(ast.getMultiplicity())).append("]");
+ }
+ for (String s: ast.getSupers()){
+ this.out.append(" : ").append(s);
+ }
+ if (ast.getElements().length > 0 || ast.getAnnotations().length > 0) {
+ this.out.append(" {\n");
+ for (SysmlAttribute annot: ast.getAnnotations()){
+ annot.accept(this, indent + indent_base);
+ }
+ for (SysmlBlockDefElement elem : ast.getElements()) {
+ elem.accept(this, indent + indent_base);
+ }
+ this.out.append(indent).append("}\n");
+ } else {
+ this.out.append(";\n");
+ }
+
+ return null;
+ }
+
+ @Override
+ public Void visit(SysmlPropertyDef ast, String indent) throws IOException {
+ this.out
+ .append(indent)
+ .append(ast.getPropertyType().getName())
+ .append(" def ")
+ .append(ast.getName())
+ ;
+ for (String s: ast.getSupers()){
+ this.out.append(" :> ").append(s);
+ }
+ if (ast.getElements().length > 0 || ast.getAnnotations().length > 0) {
+ this.out.append(" {\n");
+ for (SysmlAttribute annot: ast.getAnnotations()){
+ this.out.append(indent + indent_base).append("attribute ").append(annot.getName()).append(": ").append(annot.getRef()).append(";\n");
+ ///annot.accept(this, indent + indent_base);
+ }
+ for (SysmlBlockDefElement elem : ast.getElements()) {
+ elem.accept(this, indent + indent_base);
+ }
+ this.out.append(indent).append("}\n");
+ } else {
+ this.out.append(";\n");
+ }
+ return null;
+ }
+
+ @Override
+ public Void visit(SysmlPackage ast, String indent) throws IOException {
+ this.out.append(indent).append("package ").append(ast.getName()).append(" {").append("\n");
+ for (SysmlBlockDefElement elem : ast.getElements()) {
+ elem.accept(this, indent + indent_base);
+ }
+ this.out.append(indent).append("}\n");
+ return null;
+ }
+}
diff --git a/src/test/java/org/sysml/SysmlPropertyTest.java b/src/test/java/org/sysml/SysmlPropertyTest.java
new file mode 100644
index 00000000..6f138b8e
--- /dev/null
+++ b/src/test/java/org/sysml/SysmlPropertyTest.java
@@ -0,0 +1,117 @@
+package org.sysml;
+
+import org.clafer.ast.AstClafer;
+import org.clafer.ast.AstModel;
+import org.clafer.cli.Utils;
+import org.clafer.compiler.ClaferCompiler;
+import org.clafer.compiler.ClaferOption;
+import org.clafer.compiler.ClaferSearch;
+import org.clafer.instance.InstanceClafer;
+import org.clafer.instance.InstanceModel;
+import org.clafer.javascript.Javascript;
+import org.clafer.javascript.JavascriptFile;
+import org.clafer.objective.Objective;
+import org.clafer.scope.Scope;
+import org.junit.Test;
+import org.junit.Ignore;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.sysml.ast.SysmlPropertyDef;
+import org.sysml.compiler.AstSysmlCompiler;
+import org.sysml.compiler.InstanceSysmlCompiler;
+import org.sysml.compiler.SysmlCompilerUtils;
+import test.OptimizationTest;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertTrue;
+
+@Ignore
+@RunWith(Parameterized.class)
+public class SysmlPropertyTest {
+ @Parameterized.Parameter
+ public File testFile;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List testFiles() throws URISyntaxException {
+ File dir = new File(OptimizationTest.class.getResource("/sysml-samples/assert-positive").toURI());
+ assertTrue(dir.isDirectory());
+ List files = new ArrayList<>();
+ for (File file : dir.listFiles()) {
+ if (file.getAbsolutePath().endsWith(".cfr")) {
+ files.add(new File[]{file});
+ }
+ }
+ return files;
+ }
+
+ File getInputFile() throws IOException, InterruptedException {
+ Process compilerProcess = Runtime.getRuntime().exec("clafer -k -m choco " + testFile);
+ compilerProcess.waitFor();
+
+ // replace the extension to .js
+ String testFileName = testFile.getAbsolutePath();
+ int extPos = testFileName.lastIndexOf(".");
+ if(extPos != -1) {
+ testFileName = testFileName.substring(0, extPos) + ".js";
+ }
+
+ // change the inputFile to the resulting .js file
+ return new File(testFileName);
+ }
+
+ SysmlPropertyDef[] getSysmlPropertyDefs(File inputFile) throws IOException {
+ // compile the example
+ JavascriptFile jsFile = Javascript.readModel(inputFile);
+ AstModel top = jsFile.getModel();
+ AstSysmlCompiler compiler = new AstSysmlCompiler();
+ SysmlPropertyDef[] models = compiler.compile(top, top);
+ return models;
+ }
+
+
+ /*
+ * Test that the compiler creates SysML Properties
+ */
+ @Test
+ public void testSysmlProperty() throws IOException, URISyntaxException, InterruptedException {
+ File inputFile = getInputFile();
+ JavascriptFile jsFile = Javascript.readModel(inputFile);
+
+ Objective[] objectives = jsFile.getObjectives();
+
+ // handle scopes
+ /* setting the default int range */
+ Scope scope = jsFile.getScope();
+ int scopeHighDef = 127;
+ int scopeLowDef = -(scopeHighDef + 1);
+ scope = scope.toBuilder().intLow(scopeLowDef).intHigh(scopeHighDef).toScope();
+
+ // handle search strategy
+ ClaferOption compilerOption = jsFile.getOption();
+
+ // pick the right solver
+ ClaferSearch solver = objectives.length == 0
+ ? ClaferCompiler.compile(jsFile.getModel(), scope, compilerOption)
+ : ClaferCompiler.compile(jsFile.getModel(), scope, objectives, compilerOption);
+ assertTrue(solver.find());
+ InstanceModel instance = solver.instance();
+
+ // the instance contains something
+ assertTrue(instance.getTopClafers().length > 0);
+
+ // get its supers
+ for (InstanceClafer clafer: instance.getTopClafers()) {
+ String[] superClafers = SysmlCompilerUtils.getSuperClafers(clafer.getType());
+ List hierarchy = Arrays.asList(superClafers);
+ assertTrue(hierarchy.contains(new InstanceSysmlCompiler().baseSysmlClafer));
+ }
+
+ System.out.println(testFile);
+ }
+}
diff --git a/src/test/resources/sysml-samples/assert-positive/sysmlproperty0.cfr b/src/test/resources/sysml-samples/assert-positive/sysmlproperty0.cfr
new file mode 100644
index 00000000..1d0b1f00
--- /dev/null
+++ b/src/test/resources/sysml-samples/assert-positive/sysmlproperty0.cfr
@@ -0,0 +1,5 @@
+abstract SysmlProperty
+
+abstract Part: SysmlProperty
+
+myClafer: Part
diff --git a/src/test/resources/sysml-samples/assert-positive/sysmlproperty1.cfr b/src/test/resources/sysml-samples/assert-positive/sysmlproperty1.cfr
new file mode 100644
index 00000000..910cd904
--- /dev/null
+++ b/src/test/resources/sysml-samples/assert-positive/sysmlproperty1.cfr
@@ -0,0 +1,5 @@
+abstract SysmlProperty
+
+abstract Action: SysmlProperty
+
+myClafer: Action
diff --git a/src/test/resources/sysml-samples/assert-positive/sysmlproperty2.cfr b/src/test/resources/sysml-samples/assert-positive/sysmlproperty2.cfr
new file mode 100644
index 00000000..1305f441
--- /dev/null
+++ b/src/test/resources/sysml-samples/assert-positive/sysmlproperty2.cfr
@@ -0,0 +1,8 @@
+abstract SysmlProperty
+
+abstract Part: SysmlProperty
+
+abstract Action: SysmlProperty
+
+myClafer: Part
+ myAction: Action