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.

175 lines
5.6 KiB

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;
8 years ago
import de.joshavg.simpledic.exception.integrity.NoVisibleConstructor;
import de.joshavg.simpledic.exception.integrity.SdicClassNotFound;
import java.lang.reflect.Constructor;
8 years ago
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
* <p>
* These Checks are performed:
* <ol>
* <li>duplicated FQCNs</li>
* <li>dependency cycles</li>
* <li>dependency availability</li>
* <li>class availability</li>
* <li>constructor visibility</li>
* <li>constructor count</li>
* </ol>
*/
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(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<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) {
if (arr.length == 0) {
8 years ago
return;
}
Class<?> clz = arr[0].getDeclaringClass();
if (arr.length > 1) {
throw new MoreThanOneConstructor(clz);
}
8 years ago
if (!Modifier.isPublic(arr[0].getModifiers())) {
8 years ago
throw new NoVisibleConstructor(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(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<ServiceDefinition> getDefinitions() {
return definitions;
}
}