package org.codefx.mvn.jdeps.dependency;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import static java.lang.Integer.min;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Stream.concat;
/**
* A violation is a dependency of a class on another class which is marked as JDK-internal API by jdeps.
* <p>
* It consists of a {@link Type} which depends on one or more {@link InternalType}s.
*/
public final class Violation implements Comparable<Violation> {
private final Type dependent;
private final ImmutableList<InternalType> sortedInternalDependencies;
/**
* @throws IllegalStateException
* if the list of internal dependencies is empty
*/
private Violation(Type dependent, Collection<InternalType> internalDependencies) {
this.dependent = requireNonNull(dependent, "The argument 'dependent' must not be null.");
requireNonNull(internalDependencies, "The argument 'internalDependencies' must not be null.");
if (internalDependencies.size() == 0)
throw new IllegalArgumentException(
"A violation must contain at least one internal dependency.");
sortedInternalDependencies = sorted(internalDependencies);
}
private static ImmutableList<InternalType> sorted(Iterable<InternalType> dependencies) {
boolean dependenciesAreOrdered = Ordering.natural().isOrdered(dependencies);
if (dependenciesAreOrdered)
if (dependencies instanceof ImmutableList)
return (ImmutableList<InternalType>) dependencies;
else
return ImmutableList.copyOf(dependencies);
else
return Ordering.natural().immutableSortedCopy(dependencies);
}
/**
* Builds a new violation.
*
* @param dependent
* the dependent which contains the violating dependency
* @param internalDependencies
* the types the dependent depends upon
*
* @return a {@link ViolationBuilder}
*
* @throws IllegalStateException
* if the list of internal dependencies is empty
*/
public static Violation buildFor(Type dependent, Collection<InternalType> internalDependencies) {
return new Violation(dependent, internalDependencies);
}
/**
* Starts building a new violation.
*
* @param dependent
* the dependent which contains the violating dependency
*
* @return a {@link ViolationBuilder}
*/
public static ViolationBuilder buildForDependent(Type dependent) {
return new ViolationBuilder(dependent);
}
/**
* @return the dependent which contains the dependencies on internal types
*/
public Type getDependent() {
return dependent;
}
/**
* @return the internal types upon which {@link #getDependent()} depends
*/
public ImmutableList<InternalType> getInternalDependencies() {
return sortedInternalDependencies;
}
// #begin COMPARETO, EQUALS, HASHCODE, TOSTRING
@Override
public int compareTo(Violation other) {
if (this == other)
return 0;
int comparisonByDependents = dependent.compareTo(other.dependent);
if (comparisonByDependents != 0)
return comparisonByDependents;
// check the elements in both lists
int maxIndex = min(sortedInternalDependencies.size(), other.sortedInternalDependencies.size());
for (int i = 0; i < maxIndex; i++) {
int comparisonByDependencies =
sortedInternalDependencies.get(i).compareTo(other.sortedInternalDependencies.get(i));
if (comparisonByDependencies != 0)
return comparisonByDependencies;
}
// both lists contain the same elements up to the length of the shorter one; make the shorter one smaller
return sortedInternalDependencies.size() - other.sortedInternalDependencies.size();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Violation other = (Violation) obj;
return Objects.equals(dependent, other.dependent)
&& Objects.equals(sortedInternalDependencies, other.sortedInternalDependencies);
}
@Override
public int hashCode() {
return Objects.hash(dependent, sortedInternalDependencies);
}
@Override
public String toString() {
String dependencies = sortedInternalDependencies
.stream()
.map(Object::toString)
.collect(joining(", ", "{", "}"));
return dependent + " -> " + dependencies;
}
/**
* @return a representation of this violation that spans multiple lines
*/
public Stream<String> toLines() {
return toLines(" ", " -> ");
}
/**
* @param allPrefix
* the prefix before all lines; maybe an indent
* @param dependencyPrefix
* the prefix before the lines listing the dependencies; maybe an additional indent and an arrow
*
* @return a representation of this violation that spans multiple lines
*/
public Stream<String> toLines(String allPrefix, String dependencyPrefix) {
return concat(
Stream.of(allPrefix + dependent),
sortedInternalDependencies.stream()
.map(InternalType::toString)
.map(dependency -> allPrefix + dependencyPrefix + dependency)
);
}
// #end COMPARETO, EQUALS, HASHCODE, TOSTRING
// #begin BUILDER
/**
* Allows to build a {@link Violation} (which is immutable) by successively adding dependecies.
*/
public static class ViolationBuilder {
private final Type dependent;
private final List<InternalType> internalDependencies;
private ViolationBuilder(Type dependent) {
this.dependent = requireNonNull(dependent, "The argument 'dependent' must not be null.");
this.internalDependencies = new ArrayList<>();
}
/**
* Adds the specified {@link InternalType} as a dependency.
*
* @param dependency
* an internal dependent
*
* @return this builder
*/
public ViolationBuilder addDependency(InternalType dependency) {
requireNonNull(dependency, "The argument 'dependency' must not be null.");
internalDependencies.add(dependency);
return this;
}
/**
* Adds the specified {@link InternalType}s as dependencies.
*
* @param dependencies
* a variable number of internal types
*
* @return this builder
*/
public ViolationBuilder addDependencies(InternalType... dependencies) {
requireNonNull(dependencies, "The argument 'dependencies' must not be null.");
Collections.addAll(internalDependencies, dependencies);
return this;
}
/**
* Adds the specified {@link InternalType}s as dependencies.
*
* @param dependencies
* an iterable of internal types
*
* @return this builder
*/
public ViolationBuilder addDependencies(Iterable<InternalType> dependencies) {
requireNonNull(dependencies, "The argument 'dependencies' must not be null.");
dependencies.forEach(internalDependencies::add);
return this;
}
/**
* @return a new {@link Violation}
*
* @throws IllegalStateException
* if the list of internal dependencies is empty
*/
public Violation build() {
try {
return new Violation(dependent, internalDependencies);
} catch (IllegalArgumentException ex) {
String message = "The violation could not be built because it contains no internal dependencies. " +
"Maybe the violation block ended prematurely?";
throw new IllegalStateException(message, ex);
}
}
}
// #end BUILDER
}