Skip to content

Commit

Permalink
Initial rewrite of old structurizr-analysis library.
Browse files Browse the repository at this point in the history
  • Loading branch information
simonbrowndotje committed Aug 18, 2024
1 parent ec75900 commit 51f9041
Show file tree
Hide file tree
Showing 42 changed files with 1,373 additions and 3 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ subprojects { proj ->

description = 'Structurizr'
group = 'com.structurizr'
version = '2.2.0'
version = '2.3.0'

repositories {
mavenCentral()
Expand Down
5 changes: 3 additions & 2 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
rootProject.name = 'structurizr-java'

include 'structurizr-inspection'
include 'structurizr-autolayout'
include 'structurizr-client'
include 'structurizr-component'
include 'structurizr-core'
include 'structurizr-dsl'
include 'structurizr-export'
include 'structurizr-autolayout'
include 'structurizr-import'
include 'structurizr-inspection'
9 changes: 9 additions & 0 deletions structurizr-component/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# structurizr-component

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

This library provides a facility to discover components in a Java codebase, via a combination of
[Apache Commons BCEL](https://commons.apache.org/proper/commons-bcel/) and [JavaParser](https://javaparser.org),
using a pluggable and customisable set of matching and filtering rules.

__Unreleased, experimental, and potentially subject to change - see tests for an example.__
11 changes: 11 additions & 0 deletions structurizr-component/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
dependencies {

api project(':structurizr-core')
implementation 'org.apache.bcel:bcel:6.8.1'
implementation 'com.github.javaparser:javaparser-symbol-solver-core:3.26.1'

testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'

}

description = 'Component finder for Java code'
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.structurizr.component;

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 java.util.*;

/**
* Allows you to find components in a Java codebase based upon a set of pluggable and customisable rules.
* Use the {@link ComponentFinderBuilder} to create an instance of this class.
*/
public final class ComponentFinder {

private static final String COMPONENT_TYPE_PROPERTY_NAME = "component.type";
private static final String COMPONENT_SOURCE_PROPERTY_NAME = "component.src";

private final TypeRepository typeRepository = new TypeRepository();
private final Container container;
private final List<ComponentFinderStrategy> componentFinderStrategies = new ArrayList<>();

ComponentFinder(Container container, Collection<TypeProvider> typeProviders, List<ComponentFinderStrategy> componentFinderStrategies) {
if (container == null) {
throw new IllegalArgumentException("A container must be specified.");
}

this.container = container;

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());
}
}
}
}

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

this.componentFinderStrategies.addAll(componentFinderStrategies);
}

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);
}
}
}
}

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

for (ComponentFinderStrategy componentFinderStrategy : componentFinderStrategies) {
discoveredComponents.addAll(componentFinderStrategy.findComponents(typeRepository));
}

for (DiscoveredComponent discoveredComponent : discoveredComponents) {
Component component = container.addComponent(discoveredComponent.getName());
component.addProperty(COMPONENT_TYPE_PROPERTY_NAME, discoveredComponent.getPrimaryType().getFullyQualifiedName());
if (!StringUtils.isNullOrEmpty(discoveredComponent.getPrimaryType().getSource())) {
component.addProperty(COMPONENT_SOURCE_PROPERTY_NAME, discoveredComponent.getPrimaryType().getSource());
}
component.setDescription(discoveredComponent.getDescription());
component.setTechnology(discoveredComponent.getTechnology());
componentMap.put(discoveredComponent, component);
}

// find dependencies between all components
for (DiscoveredComponent discoveredComponent : discoveredComponents) {
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, "");
}
}
}
}
}
}

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

import com.structurizr.component.provider.DirectoryTypeProvider;
import com.structurizr.component.provider.JarFileTypeProvider;
import com.structurizr.component.provider.SourceCodeTypeProvider;
import com.structurizr.component.provider.TypeProvider;
import com.structurizr.model.Container;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
* Provides a way to create a {@link ComponentFinder} instance.
*/
public class ComponentFinderBuilder {

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

public ComponentFinderBuilder forContainer(Container container) {
this.container = container;

return this;
}

public ComponentFinderBuilder fromJarFile(String filename) {
return fromJarFile(new File(filename));
}

public ComponentFinderBuilder fromJarFile(File file) {
this.typeProviders.add(new JarFileTypeProvider(file));

return this;
}

public ComponentFinderBuilder fromDirectory(String path) {
return fromDirectory(new File(path));
}

public ComponentFinderBuilder fromDirectory(File path) {
this.typeProviders.add(new DirectoryTypeProvider(path));

return this;
}

public ComponentFinderBuilder fromSourceCode(String path) {
return fromSourceCode(new File(path));
}

public ComponentFinderBuilder fromSourceCode(File path) {
this.typeProviders.add(new SourceCodeTypeProvider(path));

return this;
}

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

return this;
}

public ComponentFinder build() {
return new ComponentFinder(container, typeProviders, componentFinderStrategies);
}

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

import com.structurizr.component.filter.TypeFilter;
import com.structurizr.component.matcher.TypeMatcher;
import com.structurizr.component.naming.NamingStrategy;
import com.structurizr.component.supporting.SupportingTypesStrategy;

import java.util.HashSet;
import java.util.Set;

/**
* A component finder strategy is a wrapper for a combination of the following:
* - {@link TypeMatcher}
* - {@link TypeFilter}
* - {@link SupportingTypesStrategy}
* - {@link NamingStrategy}
*
* Use the {@link ComponentFinderStrategyBuilder} to create an instance of this class.
*/
class ComponentFinderStrategy {

private final TypeMatcher typeMatcher;
private final TypeFilter typeFilter;
private final SupportingTypesStrategy supportingTypesStrategy;
private final NamingStrategy namingStrategy;

ComponentFinderStrategy(TypeMatcher typeMatcher, TypeFilter typeFilter, SupportingTypesStrategy supportingTypesStrategy, NamingStrategy namingStrategy) {
this.typeMatcher = typeMatcher;
this.typeFilter = typeFilter;
this.supportingTypesStrategy = supportingTypesStrategy;
this.namingStrategy = namingStrategy;
}

Set<DiscoveredComponent> findComponents(TypeRepository typeRepository) {
Set<DiscoveredComponent> components = new HashSet<>();

Set<Type> types = typeRepository.getTypes();
for (Type type : types) {
if (typeMatcher.matches(type) && typeFilter.accept(type)) {
DiscoveredComponent component = new DiscoveredComponent(namingStrategy.nameOf(type), type);
component.setDescription(type.getDescription());
component.setTechnology(typeMatcher.getTechnology());
components.add(component);

// now find supporting types
Set<Type> supportingTypes = supportingTypesStrategy.findSupportingTypes(type);
component.addSupportingTypes(supportingTypes);
}
}

return components;
}

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

import com.structurizr.component.filter.DefaultTypeFilter;
import com.structurizr.component.filter.TypeFilter;
import com.structurizr.component.matcher.TypeMatcher;
import com.structurizr.component.naming.DefaultNamingStrategy;
import com.structurizr.component.naming.NamingStrategy;
import com.structurizr.component.supporting.DefaultSupportingTypesStrategy;
import com.structurizr.component.supporting.SupportingTypesStrategy;

/**
* Provides a way to create a {@link ComponentFinderStrategy} instance.
*/
public final class ComponentFinderStrategyBuilder {

private TypeMatcher typeMatcher;
private TypeFilter typeFilter = new DefaultTypeFilter();
private SupportingTypesStrategy supportingTypesStrategy = new DefaultSupportingTypesStrategy();
private NamingStrategy namingStrategy = new DefaultNamingStrategy();

public ComponentFinderStrategyBuilder() {
}

public ComponentFinderStrategyBuilder matchedBy(TypeMatcher typeMatcher) {
this.typeMatcher = typeMatcher;

return this;
}

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

return this;
}

public ComponentFinderStrategyBuilder supportedBy(SupportingTypesStrategy supportingTypesStrategy) {
this.supportingTypesStrategy = supportingTypesStrategy;

return this;
}

public ComponentFinderStrategyBuilder namedBy(NamingStrategy namingStrategy) {
this.namingStrategy = namingStrategy;

return this;
}

public ComponentFinderStrategy build() {
if (typeMatcher == null) {
throw new RuntimeException("A type matcher must be specified");
}

return new ComponentFinderStrategy(typeMatcher, typeFilter, supportingTypesStrategy, namingStrategy);
}

}
Loading

0 comments on commit 51f9041

Please sign in to comment.