package de.joshavg.simpledic;
import de.joshavg.simpledic.exception.ClassNotRegistered;
import de.joshavg.simpledic.exception.integrity.DependencyCycleDetected;
import de.joshavg.simpledic.exception.integrity.DependencyNotSatisfied;
import de.joshavg.simpledic.exception.integrity.DuplicatedServiceClassesFound;
import de.joshavg.simpledic.exception.integrity.MoreThanOneConstructor;
import de.joshavg.simpledic.exception.integrity.NoVisibleConstructor;
import de.joshavg.simpledic.exception.integrity.SdicClassNotFound;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Takes a loaded Properties file, analyzes the {@link ServiceDefinition}s and
* checks them for sanity and integrity
*
* These Checks are performed:
*
* - duplicated FQCNs
* - dependency cycles
* - dependency availability
* - class availability
* - constructor visibility
* - constructor count
*
*/
class IntegrityCheck {
private static final Logger LOG = LoggerFactory.getLogger(IntegrityCheck.class);
private final Properties props;
private final List definitions;
IntegrityCheck(Properties props) {
this.props = props;
this.definitions = new ArrayList<>();
}
void check() {
List> services = fetchAllServices();
checkConstructorDependencies(services);
checkDuplicateServices(services);
checkCycles();
}
private void checkDuplicateServices(List> services) {
int size = services.size();
int distinctSize = services.stream().distinct().collect(Collectors.toList()).size();
if (size != distinctSize) {
throw new DuplicatedServiceClassesFound();
}
}
private List> fetchAllServices() {
return props.entrySet().stream()
.filter(e -> isServiceName(e.getKey().toString()))
.map(entry -> {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
LOG.info("found service class for name {}: {}", key, value);
try {
Class> clz = Class.forName(value);
definitions.add(new ServiceDefinition()
.setClz(clz)
.setName(key)
.setSingleton(isSingleton(key)));
return clz;
} catch (ClassNotFoundException e) {
throw new SdicClassNotFound(e);
}
}).collect(Collectors.toList());
}
private boolean isSingleton(String name) {
return props.containsKey(name + ".singleton") && "true"
.equals(props.get(name + ".singleton"));
}
private void checkConstructorDependencies(List> services) {
services.stream()
// get the first and only constructor
.map(Class::getDeclaredConstructors)
.collect(ArrayList::new,
this::collectConstructors,
List::addAll)
// get all parameter types
.stream()
.map(Constructor::getParameterTypes)
.collect((Supplier>>) ArrayList::new,
(l, arr) -> {
List> types = Arrays.asList(arr);
l.addAll(types);
},
List::addAll)
// search for services with needed types
.stream()
.distinct()
.forEach((Class> t) -> {
LOG.info("found service dependency: {}", t);
if (!services.contains(t)) {
throw new DependencyNotSatisfied(t);
}
});
}
private void collectConstructors(ArrayList l, Constructor>[] arr) {
if (arr.length == 0) {
return;
}
Class> clz = arr[0].getDeclaringClass();
if (arr.length > 1) {
throw new MoreThanOneConstructor(clz);
}
if (!Modifier.isPublic(arr[0].getModifiers())) {
throw new NoVisibleConstructor(clz);
}
l.add(arr[0]);
findDefinition(clz).setConstructor(arr[0]);
}
private ServiceDefinition findDefinition(Class> clz) {
Optional first = definitions.stream()
.filter(d -> d.getClz() == clz).findFirst();
if (!first.isPresent()) {
throw new ClassNotRegistered(clz);
}
return first.get();
}
private void checkCycles() {
definitions.forEach(def -> checkConstructorParameter(def.getClz(), null));
}
private void checkConstructorParameter(Class> rootClz, Class> paramClz) {
if (rootClz == paramClz) {
throw new DependencyCycleDetected(rootClz);
}
ServiceDefinition def;
if (paramClz == null) {
def = findDefinition(rootClz);
} else {
def = findDefinition(paramClz);
}
Class>[] parameterTypes = def.getConstructor().getParameterTypes();
for (Class> innerParamClz : parameterTypes) {
checkConstructorParameter(rootClz, innerParamClz);
}
}
static boolean isServiceName(String name) {
return name.matches("^service\\.[^.]+$");
}
List getDefinitions() {
return definitions;
}
}