Skip to content

Commit

Permalink
A few fixes, plus some refactoring to make it easier to debug what's …
Browse files Browse the repository at this point in the history
…happening when the component finder is running.
  • Loading branch information
simonbrowndotje committed Sep 18, 2024
1 parent 9529188 commit 63cb000
Show file tree
Hide file tree
Showing 46 changed files with 706 additions and 108 deletions.
12 changes: 12 additions & 0 deletions logging.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Logging
handlers = java.util.logging.ConsoleHandler
.level = INFO

java.util.logging.SimpleFormatter.format=%2$s: %5$s%n

java.util.logging.ConsoleHandler.level = ALL

com.structurizr.component.ComponentFinder.level = ALL
com.structurizr.component.TypeFinder.level = ALL
com.structurizr.component.TypeDependencyFinder.level = ALL
com.structurizr.component.ComponentFinderStrategy.level = ALL
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
rootProject.name = 'structurizr-java'

include 'structurizr-annotation'
include 'structurizr-autolayout'
include 'structurizr-client'
include 'structurizr-component'
Expand Down
4 changes: 4 additions & 0 deletions structurizr-annotation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# structurizr-annotation

[![Maven Central](https://img.shields.io/maven-central/v/com.structurizr/structurizr-annotation.svg?label=Maven%20Central)](https://search.maven.org/artifact/com.structurizr/structurizr-annotation)

3 changes: 3 additions & 0 deletions structurizr-annotation/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.structurizr.annotation;

import java.lang.annotation.*;

/**
* A wrapper for @Property annotations.
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Properties {

Property[] value();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.structurizr.annotation;

import java.lang.annotation.*;

/**
* A type-level annotation that can be used to add a name-value property to the model element represented by the type.
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Properties.class)
public @interface Property {

String name();
String value();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.structurizr.annotation;

import java.lang.annotation.*;

/**
* A type-level annotation that can be used to add a tag to the model element represented by the type.
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Tags.class)
public @interface Tag {

String name();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.structurizr.annotation;

import java.lang.annotation.*;

/**
* A wrapper for @Tag annotations.
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tags {

Tag[] value();

}
1 change: 1 addition & 0 deletions structurizr-component/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ dependencies {
implementation 'org.apache.bcel:bcel:6.8.1'
implementation 'com.github.javaparser:javaparser-symbol-solver-core:3.26.1'

testImplementation project(':structurizr-annotation')
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package com.structurizr.component;

import com.structurizr.component.filter.TypeFilter;
import com.structurizr.component.provider.TypeProvider;
import com.structurizr.model.Component;
import com.structurizr.model.Container;
import com.structurizr.util.StringUtils;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

Expand All @@ -28,83 +26,40 @@ public final class ComponentFinder {
private final Container container;
private final List<ComponentFinderStrategy> componentFinderStrategies = new ArrayList<>();

ComponentFinder(Container container, Collection<TypeProvider> typeProviders, List<ComponentFinderStrategy> componentFinderStrategies) {
ComponentFinder(Container container, TypeFilter typeFilter, Collection<TypeProvider> typeProviders, List<ComponentFinderStrategy> componentFinderStrategies) {
this.container = container;
this.componentFinderStrategies.addAll(componentFinderStrategies);

findTypes(typeProviders);
}

private void findTypes(Collection<TypeProvider> typeProviders) {
log.debug("Initialising component finder:");
log.debug(" - for: " + container.getCanonicalName());
for (TypeProvider typeProvider : typeProviders) {
Set<com.structurizr.component.Type> types = typeProvider.getTypes();
for (com.structurizr.component.Type type : types) {
if (type.getJavaClass() != null) {
// this is the BCEL identified type
typeRepository.add(type);
} else {
// this is the source code identified type
com.structurizr.component.Type bcelType = typeRepository.getType(type.getFullyQualifiedName());
if (bcelType != null) {
bcelType.setDescription(type.getDescription());
bcelType.setSource(type.getSource());
}
}
}
log.debug(" - from: " + typeProvider);
}
log.debug(" - filtered by: " + typeFilter);
for (ComponentFinderStrategy strategy : componentFinderStrategies) {
log.debug(" - with strategy: " + strategy);
}

new TypeFinder().run(typeProviders, typeFilter, typeRepository);
Repository.clearCache();
for (com.structurizr.component.Type type : typeRepository.getTypes()) {
for (Type type : typeRepository.getTypes()) {
if (type.getJavaClass() != null) {
Repository.addClass(type.getJavaClass());
findDependencies(type);
}
}
}

private void findDependencies(com.structurizr.component.Type type) {
ConstantPool cp = type.getJavaClass().getConstantPool();
ConstantPoolGen cpg = new ConstantPoolGen(cp);
for (Method m : type.getJavaClass().getMethods()) {
MethodGen mg = new MethodGen(m, type.getJavaClass().getClassName(), cpg);
InstructionList il = mg.getInstructionList();
if (il == null) {
continue;
}

InstructionHandle[] instructionHandles = il.getInstructionHandles();
for (InstructionHandle instructionHandle : instructionHandles) {
Instruction instruction = instructionHandle.getInstruction();
if (!(instruction instanceof InvokeInstruction)) {
continue;
}

InvokeInstruction invokeInstruction = (InvokeInstruction)instruction;
ReferenceType referenceType = invokeInstruction.getReferenceType(cpg);
if (!(referenceType instanceof ObjectType)) {
continue;
}

ObjectType objectType = (ObjectType)referenceType;
String referencedClassName = objectType.getClassName();
com.structurizr.component.Type referencedType = typeRepository.getType(referencedClassName);
if (referencedType != null) {
type.addDependency(referencedType);
}
new TypeDependencyFinder().run(type, typeRepository);
}
}
}

/**
* Find components, using all configured rules, in the order they were added.
* Find components, using all configured strategies, in the order they were added.
*/
public Set<Component> findComponents() {
public Set<Component> run() {
Set<DiscoveredComponent> discoveredComponents = new LinkedHashSet<>();
Map<DiscoveredComponent, Component> componentMap = new HashMap<>();
Set<Component> componentSet = new LinkedHashSet<>();

for (ComponentFinderStrategy componentFinderStrategy : componentFinderStrategies) {
Set<DiscoveredComponent> set = componentFinderStrategy.findComponents(typeRepository);
Set<DiscoveredComponent> set = componentFinderStrategy.run(typeRepository);
if (set.isEmpty()) {
throw new RuntimeException("No components were found by " + componentFinderStrategy);
}
Expand All @@ -120,28 +75,41 @@ public Set<Component> findComponents() {
component.setDescription(discoveredComponent.getDescription());
component.setTechnology(discoveredComponent.getTechnology());
component.setUrl(discoveredComponent.getUrl());

component.addTags(discoveredComponent.getTags().toArray(new String[0]));
for (String name : discoveredComponent.getProperties().keySet()) {
component.addProperty(name, discoveredComponent.getProperties().get(name));
}

componentMap.put(discoveredComponent, component);
componentSet.add(component);
}

// find dependencies between all components
for (DiscoveredComponent discoveredComponent : discoveredComponents) {
Component component = componentMap.get(discoveredComponent);
log.debug("Component dependencies for \"" + component.getName() + "\":");
Set<com.structurizr.component.Type> typeDependencies = discoveredComponent.getAllDependencies();
for (Type typeDependency : typeDependencies) {
for (DiscoveredComponent c : discoveredComponents) {
if (c != discoveredComponent) {
if (c.getAllTypes().contains(typeDependency)) {
Component componentDependency = componentMap.get(c);
componentMap.get(discoveredComponent).uses(componentDependency, "");
log.debug(" -> " + componentDependency.getName());
component.uses(componentDependency, "");
}
}
}
}
if (component.getRelationships().isEmpty()) {
log.debug(" - none");
}
}

// now visit all components
for (DiscoveredComponent discoveredComponent : componentMap.keySet()) {
Component component = componentMap.get(discoveredComponent);
log.debug("Visiting \"" + component.getName() + "\"");
discoveredComponent.getComponentFinderStrategy().visit(component);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.structurizr.component;

import com.structurizr.component.filter.DefaultTypeFilter;
import com.structurizr.component.filter.TypeFilter;
import com.structurizr.component.provider.ClassDirectoryTypeProvider;
import com.structurizr.component.provider.ClassJarFileTypeProvider;
import com.structurizr.component.provider.SourceDirectoryTypeProvider;
Expand All @@ -19,6 +21,7 @@ public class ComponentFinderBuilder {

private Container container;
private final List<TypeProvider> typeProviders = new ArrayList<>();
private TypeFilter typeFilter;
private final List<ComponentFinderStrategy> componentFinderStrategies = new ArrayList<>();

public ComponentFinderBuilder forContainer(Container container) {
Expand Down Expand Up @@ -57,6 +60,12 @@ public ComponentFinderBuilder fromSource(File path) {
return this;
}

public ComponentFinderBuilder filteredBy(TypeFilter typeFilter) {
this.typeFilter = typeFilter;

return this;
}

public ComponentFinderBuilder withStrategy(ComponentFinderStrategy componentFinderStrategy) {
this.componentFinderStrategies.add(componentFinderStrategy);

Expand All @@ -72,11 +81,25 @@ public ComponentFinder build() {
throw new RuntimeException("One or more type providers must be configured");
}

if (typeFilter == null) {
typeFilter = new DefaultTypeFilter();
}

if (componentFinderStrategies.isEmpty()) {
throw new RuntimeException("One or more component finder strategies must be configured");
}

return new ComponentFinder(container, typeProviders, componentFinderStrategies);
return new ComponentFinder(container, typeFilter, typeProviders, componentFinderStrategies);
}

@Override
public String toString() {
return "ComponentFinderBuilder{" +
"container=" + container +
", typeProviders=" + typeProviders +
", typeFilter=" + typeFilter +
", componentFinderStrategies=" + componentFinderStrategies +
'}';
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
import com.structurizr.component.url.UrlStrategy;
import com.structurizr.component.visitor.ComponentVisitor;
import com.structurizr.model.Component;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/**
Expand All @@ -23,6 +26,8 @@
*/
public final class ComponentFinderStrategy {

private static final Log log = LogFactory.getLog(ComponentFinderStrategy.class);

private final String technology;
private final TypeMatcher typeMatcher;
private final TypeFilter typeFilter;
Expand All @@ -43,22 +48,49 @@ public final class ComponentFinderStrategy {
this.componentVisitor = componentVisitor;
}

Set<DiscoveredComponent> findComponents(TypeRepository typeRepository) {
Set<DiscoveredComponent> run(TypeRepository typeRepository) {
Set<DiscoveredComponent> components = new LinkedHashSet<>();
log.debug("Running " + this.toString());

Set<Type> types = typeRepository.getTypes();
for (Type type : types) {
if (typeMatcher.matches(type) && typeFilter.accept(type)) {

boolean matched = typeMatcher.matches(type);
boolean accepted = typeFilter.accept(type);

if (matched) {
if (accepted) {
log.debug(" + " + type.getFullyQualifiedName() + " (matched=true, accepted=true)");
} else {
log.debug(" - " + type.getFullyQualifiedName() + " (matched=true, accepted=false)");
}
} else {
log.debug(" - " + type.getFullyQualifiedName() + " (matched=false)");
}

if (matched && accepted) {
DiscoveredComponent component = new DiscoveredComponent(namingStrategy.nameOf(type), type);
component.setDescription(descriptionStrategy.descriptionOf(type));
component.setTechnology(this.technology);
component.setUrl(urlStrategy.urlOf(type));
component.addTags(type.getTags());
Map<String, String> properties = type.getProperties();
for (String name : properties.keySet()) {
component.addProperty(name, properties.get(name));
}
component.setComponentFinderStrategy(this);
components.add(component);

// now find supporting types
Set<Type> supportingTypes = supportingTypesStrategy.findSupportingTypes(type, typeRepository);
component.addSupportingTypes(supportingTypes);
if (supportingTypes.isEmpty()) {
log.debug(" - none");
} else {
for (Type supportingType : supportingTypes) {
log.debug(" + supporting type: " + supportingType.getFullyQualifiedName());
}
component.addSupportingTypes(supportingTypes);
}
}
}

Expand Down
Loading

0 comments on commit 63cb000

Please sign in to comment.