You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
4.8 KiB
143 lines
4.8 KiB
![]()
8 years ago
|
package de.joshavg.simpledic;
|
||
|
|
||
|
import com.google.common.annotations.VisibleForTesting;
|
||
|
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.SdicClassNotFound;
|
||
|
import java.lang.reflect.Constructor;
|
||
|
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;
|
||
|
|
||
|
class IntegrityCheck {
|
||
|
|
||
|
private static final Logger LOG = LoggerFactory.getLogger(IntegrityCheck.class);
|
||
|
|
||
|
private final Properties props;
|
||
|
private final List<ServiceDefinition> definitions;
|
||
|
|
||
|
IntegrityCheck(Properties props) {
|
||
|
this.props = props;
|
||
|
this.definitions = new ArrayList<>();
|
||
|
}
|
||
|
|
||
|
void check() {
|
||
|
List<Class<?>> services = fetchAllServices();
|
||
|
checkConstructorDependencies(services);
|
||
|
checkDuplicateServices(services);
|
||
|
checkCycles();
|
||
|
}
|
||
|
|
||
|
private void checkDuplicateServices(List<Class<?>> services) {
|
||
|
int size = services.size();
|
||
|
int distinctSize = services.stream().distinct().collect(Collectors.toList()).size();
|
||
|
if (size != distinctSize) {
|
||
|
throw new DuplicatedServiceClassesFound();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private List<Class<?>> 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(value));
|
||
|
return clz;
|
||
|
} catch (ClassNotFoundException e) {
|
||
|
throw new SdicClassNotFound(e);
|
||
|
}
|
||
|
}).collect(Collectors.toList());
|
||
|
}
|
||
|
|
||
|
private void checkConstructorDependencies(List<Class<?>> 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<Class<?>>>) ArrayList::new,
|
||
|
(l, arr) -> {
|
||
|
List<Class<?>> 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<Constructor> l, Constructor<?>[] arr) {
|
||
|
Class<?> clz = arr[0].getDeclaringClass();
|
||
|
if (arr.length > 1) {
|
||
|
throw new MoreThanOneConstructor(clz);
|
||
|
}
|
||
|
l.add(arr[0]);
|
||
|
findDefinition(clz).setConstructor(arr[0]);
|
||
|
}
|
||
|
|
||
|
private ServiceDefinition findDefinition(Class<?> clz) {
|
||
|
Optional<ServiceDefinition> 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(null, def.getClz());
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private void checkConstructorParameter(Class<?> rootClz, Class<?> paramClz) {
|
||
|
if (rootClz == paramClz) {
|
||
|
throw new DependencyCycleDetected(rootClz);
|
||
|
}
|
||
|
|
||
|
Class<?>[] parameterTypes = findDefinition(paramClz).getConstructor().getParameterTypes();
|
||
|
for (Class<?> innerParamClz : parameterTypes) {
|
||
|
if (rootClz == null) {
|
||
|
checkConstructorParameter(paramClz, innerParamClz);
|
||
|
} else {
|
||
|
checkConstructorParameter(rootClz, innerParamClz);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@VisibleForTesting
|
||
|
static boolean isServiceName(String name) {
|
||
|
return !"service.".equals(name) && name.startsWith("service.");
|
||
|
}
|
||
|
|
||
|
List<ServiceDefinition> getDefinitions() {
|
||
|
return definitions;
|
||
|
}
|
||
|
}
|