Skip to content

Commit

Permalink
moditect#28 Allowing to pre-define components during analyze
Browse files Browse the repository at this point in the history
  • Loading branch information
gunnarmorling committed Jan 23, 2019
1 parent 8473ba0 commit 9e5b565
Show file tree
Hide file tree
Showing 18 changed files with 531 additions and 54 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,14 @@ See [integration-test/build.gradle](build.gradle) for a complete example.
* `-Adeptective.mode=(ANALYZE|VALIDATE)`: Whether the plug-in should validate the packages of the compiled package against the _deptective.json_ file (`VALIDATE`) or whether it should generate a template for that file based on the current actual package relationships (`ANALYZE`).
The latter can be useful when introducing Deptective into an existing code base where writing the configuration from scratch might be too tedious. Generating the configuration from the current "is" state and iteratively refining it into an intended target state can be a useful approach in that case.
The generated JSON/DOT files are created in the compiler's class output path, e.g. _target/classes_ in case of Maven. Defaults to `VALIDATE`
* `-Adeptective.whitelisted=...`: A comma-separated list of whitelist package patterns which will be applied in `ANALYZE` mode. Any reference to a whitelisted package will then not be added to the `reads` section of the referencing package in the generated descriptor template.
* `-Adeptective.whitelisted=...`: An optional comma-separated list of whitelist package patterns which will be applied in `ANALYZE` mode. Any reference to a whitelisted package will then not be added to the `reads` section of the referencing package in the generated descriptor template.
The special value `*ALL_EXTERNAL*` can be used to automatically whitelist all packages which are not part of the current compilation (i.e. packages from dependencies). This can be useful if you're only interested in managing the relationships amongst the current project's packages themselves but not the relationships to external packages.
* `-Adeptective.components=...`: A optional semicolon-separated list of component definitions which will be applied in `ANALYZE` mode.
This is helpful when creating the Deptective configuration for an existing code base,
where examining relationships on the package level would be too detailed otherwise.
Component definitions are given in the form "<name>:<package pattern 1>, <package pattern 2>, ...".
Any package matching a component will not be added by itself to the generate configuration but to the `contains` section of the matching component.
Example value: "foo:com.example.foo1,com.example.foo2;bar:com.example.bar*;qux:com.example.qux".
* `-Adeptective.visualize=(true|false)`: Whether to create a GraphVize (DOT) file representing generated configuration template (in `ANALYZE` mode) or the dependency configuration and (if present) any illegal package dependencies (in `VALIDATE` mode). Defaults to `false`

### Obtaining Deptective via Jitpack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@
*/
package org.moditect.deptective.internal;

import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.tools.JavaFileManager;

import org.moditect.deptective.internal.handler.PackageReferenceCollector;
import org.moditect.deptective.internal.handler.PackageReferenceHandler;
import org.moditect.deptective.internal.handler.PackageReferenceValidator;
import org.moditect.deptective.internal.log.Log;
import org.moditect.deptective.internal.model.Component;
import org.moditect.deptective.internal.model.Components;
import org.moditect.deptective.internal.model.PackageDependencies;
import org.moditect.deptective.internal.options.DeptectiveOptions;

Expand Down Expand Up @@ -54,10 +58,21 @@ public PackageReferenceHandler getPackageReferenceHandler(JavaFileManager jfm, D
@Override
public PackageReferenceHandler getPackageReferenceHandler(JavaFileManager jfm, DeptectiveOptions options,
Supplier<PackageDependencies> configSupplier, Log log) {

Set<Component> components = options.getComponentPackagePatterns()
.entrySet()
.stream()
.map(e -> new Component.Builder(e.getKey())
.addContains(e.getValue())
.build()
)
.collect(Collectors.toSet());

return new PackageReferenceCollector(
jfm,
log,
options.getWhitelistedPackagePatterns(),
new Components(components),
options.createDotFile()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@
import org.moditect.deptective.internal.export.ModelSerializer;
import org.moditect.deptective.internal.log.DeptectiveMessages;
import org.moditect.deptective.internal.log.Log;
import org.moditect.deptective.internal.model.Component;
import org.moditect.deptective.internal.model.Components;
import org.moditect.deptective.internal.model.PackageAssignedToMultipleComponentsException;
import org.moditect.deptective.internal.model.PackageDependencies;
import org.moditect.deptective.internal.model.PackagePattern;
import org.moditect.deptective.internal.model.ReadKind;
import org.moditect.deptective.internal.options.ReportingPolicy;

import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
Expand All @@ -54,22 +58,34 @@ public class PackageReferenceCollector implements PackageReferenceHandler {

private final JavaFileManager jfm;
private final List<PackagePattern> whitelistPatterns;

/**
* Any components that were declared externally.
*/
private final Components declaredComponents;
private final Set<String> packagesOfCurrentCompilation;
private final Set<String> referencedPackages;

private String currentPackageName;
private Component currentComponent;
private boolean createOutputFile = true;

public PackageReferenceCollector(JavaFileManager jfm, Log log, List<PackagePattern> whitelistPatterns,
boolean createDotFile) {
Components declaredComponents, boolean createDotFile) {
this.log = log;
this.jfm = jfm;
this.whitelistPatterns = Collections.unmodifiableList(whitelistPatterns);
this.declaredComponents = declaredComponents;
this.createDotFile = createDotFile;

this.packagesOfCurrentCompilation = new HashSet<String>();
this.referencedPackages = new HashSet<String>();

builder = PackageDependencies.builder();

for (Component component : declaredComponents) {
builder.addComponent(component.getName(), component.getContained(), Collections.emptyList());
}
}

@Override
Expand All @@ -83,18 +99,55 @@ public boolean onEnteringCompilationUnit(CompilationUnitTree tree) {

currentPackageName = packageNameTree.toString();
packagesOfCurrentCompilation.add(currentPackageName);
builder.addContains(currentPackageName, PackagePattern.getPattern(currentPackageName));

try {
currentComponent = declaredComponents.getComponentByPackage(currentPackageName);
}
catch (PackageAssignedToMultipleComponentsException e) {
log.report(
ReportingPolicy.ERROR,
DeptectiveMessages.PACKAGE_CONTAINED_IN_MULTIPLE_COMPONENTS,
String.join(
", ",
e.getMatchingComponents()
.stream()
.map(Component::getName)
.sorted()
.collect(Collectors.toList())
),
currentPackageName
);

createOutputFile = false;
return false;
}

if (currentComponent == null) {
builder.addContains(currentPackageName, PackagePattern.getPattern(currentPackageName));
}

return true;
}

@Override
public void onPackageReference(Tree referencingNode, String referencedPackageName) {
referencedPackages.add(referencedPackageName);
builder.addRead(currentPackageName, referencedPackageName, ReadKind.ALLOWED);

Component referencedComponent = declaredComponents.getComponentByPackage(referencedPackageName);

builder.addRead(
currentComponent != null ? currentComponent.getName() : currentPackageName,
referencedComponent != null ? referencedComponent.getName() : referencedPackageName,
ReadKind.ALLOWED
);
}

@Override
public void onCompletingCompilation() {
if (!createOutputFile) {
return;
}

List<PackagePattern> effectiveWhitelistPatterns;

if (isWhitelistAllExternal()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public class PackageReferenceValidator implements PackageReferenceHandler {
private final PackageDependencies allowedPackageDependencies;
private final JavaFileManager jfm;
private final ReportingPolicy reportingPolicy;
private final boolean createDotFile;
private boolean createDotFile;
private final ReportingPolicy unconfiguredPackageReportingPolicy;
private final Map<String, Boolean> reportedUnconfiguredPackages;

Expand Down Expand Up @@ -116,6 +116,7 @@ public boolean onEnteringCompilationUnit(CompilationUnitTree tree) {
packageName
);

createDotFile = false;
return false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Copyright 2019 The ModiTect authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.moditect.deptective.internal.model;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* A set of {@link Component}s.
*
* @author Gunnar Morling
*/
public class Components implements Iterable<Component> {

private final Set<Component> contained;
private final Map<String, Component> componentsByPackage;

public Components(Set<Component> contained) {
this.contained = Collections.unmodifiableSet(contained);
this.componentsByPackage = new HashMap<>();
}

@Override
public Iterator<Component> iterator() {
return contained.iterator();
}

public Stream<Component> stream() {
return contained.stream();
}

/**
* Returns the component containing the given package or {@code null} if no such component exists.
*
* @throws PackageAssignedToMultipleComponentsException In case more than one component was found whose filter
* expressions match the given package.
*/
public Component getComponentByPackage(String qualifiedName) throws PackageAssignedToMultipleComponentsException {
if (qualifiedName == null || qualifiedName.isEmpty()) {
return null;
}

return componentsByPackage.computeIfAbsent(
qualifiedName,
p -> {
Set<Component> candidates = contained.stream()
.filter(c -> c.containsPackage(p))
.collect(Collectors.toSet());

if (candidates.isEmpty()) {
return null;
}
else if (candidates.size() == 1) {
return candidates.iterator().next();
}
else {
throw new PackageAssignedToMultipleComponentsException(candidates);
}
}
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
*/
package org.moditect.deptective.internal.model;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
Expand All @@ -33,16 +33,15 @@ public static class Builder {
private final Set<PackagePattern> whitelisted = new HashSet<>();

public PackageDependencies build() {
return new PackageDependencies(
componentsByName.values()
.stream()
.map(Component.Builder::build)
.collect(Collectors.toSet()),
whitelisted
);
Set<Component> components = componentsByName.values()
.stream()
.map(Component.Builder::build)
.collect(Collectors.toSet());

return new PackageDependencies(new Components(components), whitelisted);
}

public void addComponent(String name, List<PackagePattern> contains, List<String> reads) {
public void addComponent(String name, Collection<PackagePattern> contains, Collection<String> reads) {
if (componentsByName.containsKey(name)) {
throw new IllegalArgumentException("Component " + name + " may not be configured more than once.");
}
Expand Down Expand Up @@ -78,13 +77,11 @@ public void addWhitelistedPackage(PackagePattern pattern) {
}
}

private final Set<Component> components;
private final Map<String, Component> componentsByPackage;
private final Components components;
private final Set<PackagePattern> whitelisted;

private PackageDependencies(Set<Component> components, Set<PackagePattern> whitelisted) {
this.components = Collections.unmodifiableSet(components);
this.componentsByPackage = new HashMap<>();
private PackageDependencies(Components components, Set<PackagePattern> whitelisted) {
this.components = components;
this.whitelisted = Collections.unmodifiableSet(whitelisted);
}

Expand All @@ -99,28 +96,7 @@ public static Builder builder() {
* expressions match the given package.
*/
public Component getComponentByPackage(String qualifiedName) throws PackageAssignedToMultipleComponentsException {
if (qualifiedName == null || qualifiedName.isEmpty()) {
return null;
}

return componentsByPackage.computeIfAbsent(
qualifiedName,
p -> {
Set<Component> candidates = components.stream()
.filter(c -> c.containsPackage(p))
.collect(Collectors.toSet());

if (candidates.isEmpty()) {
return null;
}
else if (candidates.size() == 1) {
return candidates.iterator().next();
}
else {
throw new PackageAssignedToMultipleComponentsException(candidates);
}
}
);
return components.getComponentByPackage(qualifiedName);
}

@Override
Expand Down
Loading

0 comments on commit 9e5b565

Please sign in to comment.