Skip to content

Commit

Permalink
Merge pull request #135 from TNG/enhance-plant-uml
Browse files Browse the repository at this point in the history
Enhance supported PlantUML syntax
  • Loading branch information
codecholeric authored Dec 16, 2018
2 parents 0339812 + 2c09b52 commit 787712f
Show file tree
Hide file tree
Showing 16 changed files with 281 additions and 100 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.tngtech.archunit.exampletest.junit4;

import java.net.URL;
import java.util.Set;

import com.tngtech.archunit.base.PackageMatchers;
import com.tngtech.archunit.example.shopping.catalog.ProductCatalog;
import com.tngtech.archunit.example.shopping.order.Order;
import com.tngtech.archunit.example.shopping.product.Product;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.junit.ArchUnitRunner;
Expand All @@ -13,6 +14,7 @@
import org.junit.runner.RunWith;

import static com.tngtech.archunit.core.domain.Dependency.Predicates.dependency;
import static com.tngtech.archunit.core.domain.JavaClass.Functions.GET_PACKAGE_NAME;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.library.plantuml.PlantUmlArchCondition.Configurations.consideringAllDependencies;
Expand All @@ -34,9 +36,10 @@ public class PlantUmlArchitectureTest {
public static final ArchRule classes_should_adhere_to_shopping_example_considering_all_dependencies_and_ignoring_some_dependencies =
classes().should(adhereToPlantUmlDiagram(plantUmlDiagram, consideringAllDependencies())
.ignoreDependenciesWithOrigin(equivalentTo(ProductCatalog.class))
.ignoreDependenciesWithTarget(equivalentTo(Object.class))
.ignoreDependencies(dependency(Order.class, Set.class)
.as(String.format("ignoring dependencies from %s to %s", Order.class.getName(), Set.class.getName()))));
.ignoreDependenciesWithTarget(GET_PACKAGE_NAME.is(PackageMatchers.of("", "java..")).as("that is part of JDK"))
.ignoreDependencies(dependency(Product.class, Order.class)
.as(String.format("ignoring dependencies from %s to %s", Product.class.getName(), Order.class.getName()))));

@ArchTest
public static final ArchRule classes_should_adhere_to_shopping_example_considering_only_dependencies_in_any_package =
classes().should(adhereToPlantUmlDiagram(plantUmlDiagram,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ skinparam component {
' Could be some random comment
[XML] <<..xml.processor..>> <<..xml.types..>> as xml

[Order] --> [Customer]
[Order] ---> [Customer] : is placed by
[Order] --> [Products]

[Customer] --> [Address]

catalog ---> [Products]
import --> catalog
[Products] <--[#green]- catalog
import -left-> catalog : parse products
import --> xml
note top on link #lightgreen: is responsible for translating XML to java classes

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.tngtech.archunit.exampletest.junit5;

import java.net.URL;
import java.util.Set;

import com.tngtech.archunit.base.PackageMatchers;
import com.tngtech.archunit.example.shopping.catalog.ProductCatalog;
import com.tngtech.archunit.example.shopping.order.Order;
import com.tngtech.archunit.example.shopping.product.Product;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTag;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.core.domain.Dependency.Predicates.dependency;
import static com.tngtech.archunit.core.domain.JavaClass.Functions.GET_PACKAGE_NAME;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.library.plantuml.PlantUmlArchCondition.Configurations.consideringAllDependencies;
Expand All @@ -31,9 +33,9 @@ public class PlantUmlArchitectureTest {
static final ArchRule classes_should_adhere_to_shopping_example_considering_all_dependencies_and_ignoring_some_dependencies =
classes().should(adhereToPlantUmlDiagram(plantUmlDiagram, consideringAllDependencies())
.ignoreDependenciesWithOrigin(equivalentTo(ProductCatalog.class))
.ignoreDependenciesWithTarget(equivalentTo(Object.class))
.ignoreDependencies(dependency(Order.class, Set.class)
.as(String.format("ignoring dependencies from %s to %s", Order.class.getName(), Set.class.getName()))));
.ignoreDependenciesWithTarget(GET_PACKAGE_NAME.is(PackageMatchers.of("", "java..")).as("that is part of JDK"))
.ignoreDependencies(dependency(Product.class, Order.class)
.as(String.format("ignoring dependencies from %s to %s", Product.class.getName(), Order.class.getName()))));

@ArchTest
static final ArchRule classes_should_adhere_to_shopping_example_considering_only_dependencies_in_any_package =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ skinparam component {
' Could be some random comment
[XML] <<..xml.processor..>> <<..xml.types..>> as xml

[Order] --> [Customer]
[Order] ---> [Customer] : is placed by
[Order] --> [Products]

[Customer] --> [Address]

catalog ---> [Products]
import --> catalog
[Products] <--[#green]- catalog
import -left-> catalog : parse products
import --> xml
note top on link #lightgreen: is responsible for translating XML to java classes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
import com.tngtech.archunit.example.shopping.product.Product;

public class ProductCatalog {
public Set<Product> allProducts;
private Set<Product> allProducts;

void gonnaDoSomethingIllegalWithOrder() {
Order order = new Order();
for (Product product : allProducts) {
product.register();
}
order.addProducts(allProducts);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package com.tngtech.archunit.example.shopping.customer;

import com.tngtech.archunit.example.shopping.address.Address;
import com.tngtech.archunit.example.shopping.order.Order;

public class Customer {
public Address address;
private Address address;

void addOrder(Order order) {
// simply having such a parameter violates the specified UML diagram
}

public Address getAddress() {
return address;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ public class ProductImport {
public XmlProcessor xmlProcessor;

public Customer getCustomer() {
return new Customer();
return new Customer(); // violates diagram -> product import may not directly know Customer
}

ProductCatalog parse(byte[] xml) {
return new ProductCatalog();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@

import java.util.Set;

import com.tngtech.archunit.example.shopping.address.Address;
import com.tngtech.archunit.example.shopping.customer.Customer;
import com.tngtech.archunit.example.shopping.product.Product;

public class Order {
public Customer customer;
public Set<Product> products;
private Set<Product> products;

public void addProducts(Set<Product> products) {
this.products.addAll(products);
}

void report() {
report(customer.getAddress());
for (Product product : products) {
product.report();
}
}

private void report(Address address) {
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package com.tngtech.archunit.example.shopping.product;

import com.tngtech.archunit.example.shopping.customer.Customer;
import com.tngtech.archunit.example.shopping.order.Order;

public class Product {
public Customer customer;

Order getOrder() {
return null; // the return type violates the specified UML diagram
}

public void register() {
}

public void report() {
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.tngtech.archunit.exampletest;

import java.net.URL;
import java.util.Set;

import com.tngtech.archunit.base.PackageMatchers;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.example.shopping.catalog.ProductCatalog;
import com.tngtech.archunit.example.shopping.order.Order;
import com.tngtech.archunit.example.shopping.product.Product;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import static com.tngtech.archunit.core.domain.Dependency.Predicates.dependency;
import static com.tngtech.archunit.core.domain.JavaClass.Functions.GET_PACKAGE_NAME;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.library.plantuml.PlantUmlArchCondition.Configurations.consideringAllDependencies;
Expand All @@ -33,9 +35,9 @@ public void classes_should_adhere_to_shopping_example_considering_only_dependenc
public void classes_should_adhere_to_shopping_example_considering_all_dependencies_and_ignoring_some_dependencies() {
classes().should(adhereToPlantUmlDiagram(plantUmlDiagram, consideringAllDependencies())
.ignoreDependenciesWithOrigin(equivalentTo(ProductCatalog.class))
.ignoreDependenciesWithTarget(equivalentTo(Object.class))
.ignoreDependencies(dependency(Order.class, Set.class)
.as(String.format("ignoring dependencies from %s to %s", Order.class.getName(), Set.class.getName()))))
.ignoreDependenciesWithTarget(GET_PACKAGE_NAME.is(PackageMatchers.of("", "java..")).as("that is part of JDK"))
.ignoreDependencies(dependency(Product.class, Order.class)
.as(String.format("ignoring dependencies from %s to %s", Product.class.getName(), Order.class.getName()))))
.check(classes);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ skinparam component {
' Could be some random comment
[XML] <<..xml.processor..>> <<..xml.types..>> as xml

[Order] --> [Customer]
[Order] ---> [Customer] : is placed by
[Order] --> [Products]

[Customer] --> [Address]

catalog ---> [Products]
import --> catalog
[Products] <--[#green]- catalog
import -left-> catalog : parse products
import --> xml
note top on link #lightgreen: is responsible for translating XML to java classes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -769,28 +769,38 @@ Stream<DynamicTest> PlantUmlArchitectureTest() {
.ofType(ProductCatalog.class))
.by(field(Product.class, "customer")
.ofType(Customer.class))
.by(method(Product.class, "getOrder")
.withReturnType(Order.class))
.by(method(Customer.class, "addOrder")
.withParameter(Order.class))
.by(callFromMethod(ProductCatalog.class, "gonnaDoSomethingIllegalWithOrder")
.toConstructor(Order.class).inLine(12).asDependency())
.by(callFromMethod(ProductCatalog.class, "gonnaDoSomethingIllegalWithOrder")
.toMethod(Order.class, "addProducts", Set.class).inLine(13).asDependency())
.toMethod(Order.class, "addProducts", Set.class).inLine(16).asDependency())
.by(callFromMethod(ProductImport.class, "getCustomer")
.toConstructor(Customer.class).inLine(14).asDependency())
.by(method(ProductImport.class, "getCustomer")
.withReturnType(Customer.class))
.by(method(Order.class, "report")
.withParameter(Address.class))

.ofRule(String.format("classes should adhere to PlantUML diagram <shopping_example.puml>,"
+ " ignoring dependencies with origin equivalent to %s,"
+ " ignoring dependencies with target equivalent to %s,"
+ " ignoring dependencies with target that is part of JDK,"
+ " ignoring dependencies from %s to %s",
ProductCatalog.class.getName(), Object.class.getName(), Order.class.getName(), Set.class.getName()))
ProductCatalog.class.getName(), Product.class.getName(), Order.class.getName()))
.by(field(Address.class, "productCatalog")
.ofType(ProductCatalog.class))
.by(field(Product.class, "customer")
.ofType(Customer.class))
.by(method(Customer.class, "addOrder")
.withParameter(Order.class))
.by(callFromMethod(ProductImport.class, "getCustomer")
.toConstructor(Customer.class).inLine(14).asDependency())
.by(method(ProductImport.class, "getCustomer")
.withReturnType(Customer.class))
.by(method(Order.class, "report")
.withParameter(Address.class))

.toDynamicTests();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.CharStreams;
Expand All @@ -30,6 +31,8 @@
import com.tngtech.archunit.library.plantuml.PlantUmlPatterns.PlantUmlDependencyMatcher;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.containsPattern;
import static com.google.common.base.Predicates.not;
import static java.nio.charset.StandardCharsets.UTF_8;

class PlantUmlParser {
Expand All @@ -40,17 +43,22 @@ PlantUmlDiagram parse(URL url) {
return createDiagram(readLines(url));
}

private PlantUmlDiagram createDiagram(List<String> umlTextAsList) {
Set<PlantUmlComponent> components = parseComponents(umlTextAsList);
private PlantUmlDiagram createDiagram(List<String> rawDiagramLines) {
List<String> diagramLines = filterOutComments(rawDiagramLines);
Set<PlantUmlComponent> components = parseComponents(diagramLines);
PlantUmlComponents plantUmlComponents = new PlantUmlComponents(components);

List<ParsedDependency> dependencies = parseDependencies(plantUmlComponents, umlTextAsList);
List<ParsedDependency> dependencies = parseDependencies(plantUmlComponents, diagramLines);

return new PlantUmlDiagram.Builder(plantUmlComponents)
.withDependencies(dependencies)
.build();
}

private List<String> filterOutComments(List<String> lines) {
return FluentIterable.from(lines).filter(not(containsPattern("^\\s*'"))).toList();
}

private List<String> readLines(URL url) {
try (InputStreamReader in = new InputStreamReader(url.openStream(), UTF_8)) {
return CharStreams.readLines(in);
Expand All @@ -66,9 +74,13 @@ private Set<PlantUmlComponent> parseComponents(List<String> plantUmlDiagramLines
}

private ImmutableList<ParsedDependency> parseDependencies(PlantUmlComponents plantUmlComponents, List<String> plantUmlDiagramLines) {
return plantUmlPatterns.filterDependencies(plantUmlDiagramLines)
.transform(toPlantUmlDependency(plantUmlComponents))
.toList();
ImmutableList.Builder<ParsedDependency> result = ImmutableList.builder();
for (PlantUmlDependencyMatcher matcher : plantUmlPatterns.matchDependencies(plantUmlDiagramLines)) {
PlantUmlComponent origin = findComponentMatching(plantUmlComponents, matcher.matchOrigin());
PlantUmlComponent target = findComponentMatching(plantUmlComponents, matcher.matchTarget());
result.add(new ParsedDependency(origin.getIdentifier(), target.getIdentifier()));
}
return result.build();
}

private Function<String, PlantUmlComponent> toPlantUmlComponent() {
Expand All @@ -80,15 +92,6 @@ public PlantUmlComponent apply(String input) {
};
}

private Function<String, ParsedDependency> toPlantUmlDependency(final PlantUmlComponents plantUmlComponents) {
return new Function<String, ParsedDependency>() {
@Override
public ParsedDependency apply(String input) {
return createNewDependency(plantUmlComponents, input);
}
};
}

private PlantUmlComponent createNewComponent(String input) {
PlantUmlComponentMatcher matcher = plantUmlPatterns.matchComponent(input);

Expand Down Expand Up @@ -117,20 +120,10 @@ private ImmutableSet<Stereotype> identifyStereotypes(PlantUmlComponentMatcher ma
return result;
}

private ParsedDependency createNewDependency(PlantUmlComponents plantUmlComponents, String input) {
PlantUmlDependencyMatcher matcher = plantUmlPatterns.matchDependency(input);

PlantUmlComponent origin = findComponentMatching(plantUmlComponents, matcher.matchOrigin());
PlantUmlComponent target = findComponentMatching(plantUmlComponents, matcher.matchTarget());

return new ParsedDependency(origin.getIdentifier(), target.getIdentifier());
}

private PlantUmlComponent findComponentMatching(PlantUmlComponents plantUmlComponents, String originOrTargetString) {
originOrTargetString = originOrTargetString.trim()
.replaceAll("^\\[", "")
.replaceAll("]$", "")
.replaceAll("\"", "");
.replaceAll("]$", "");

return plantUmlComponents.findComponentWith(originOrTargetString);
}
Expand Down
Loading

0 comments on commit 787712f

Please sign in to comment.