parent
afdab8cf20
commit
e616a53540
@ -0,0 +1,11 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
max_line_length = 120
|
@ -0,0 +1,31 @@
|
|||||||
|
package de.joshavg.simpledic;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
class Instantiator<T> {
|
||||||
|
|
||||||
|
private final Class<T> clz;
|
||||||
|
|
||||||
|
Instantiator(Class<T> clz) {
|
||||||
|
this.clz = clz;
|
||||||
|
}
|
||||||
|
|
||||||
|
T createInstance()
|
||||||
|
throws IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Constructor<T> constructor = (Constructor<T>) clz.getDeclaredConstructors()[0];
|
||||||
|
|
||||||
|
Class<?>[] parameterTypes = constructor.getParameterTypes();
|
||||||
|
Object[] parameters = new Object[parameterTypes.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < parameterTypes.length; ++i) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<Object> paramClz = (Class<Object>) parameterTypes[i];
|
||||||
|
parameters[i] = new Instantiator<>(paramClz).createInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return constructor.newInstance(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package de.joshavg.simpledic;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
|
class ServiceDefinition {
|
||||||
|
|
||||||
|
private Class<?> clz;
|
||||||
|
private String name;
|
||||||
|
private Constructor<?> constructor;
|
||||||
|
|
||||||
|
public Class<?> getClz() {
|
||||||
|
return clz;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceDefinition setClz(Class<?> clz) {
|
||||||
|
this.clz = clz;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceDefinition setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Constructor<?> getConstructor() {
|
||||||
|
return constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceDefinition setConstructor(Constructor<?> constructor) {
|
||||||
|
this.constructor = constructor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package de.joshavg.simpledic.exception;
|
||||||
|
|
||||||
|
public class ClassNotRegistered extends RuntimeException {
|
||||||
|
|
||||||
|
public ClassNotRegistered(Class<?> clz) {
|
||||||
|
super(String.format("class not registered in container: %s", clz));
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package de.joshavg.simpledic.exception;
|
|
||||||
|
|
||||||
public class DependencyNotSatisfiedException extends RuntimeException {
|
|
||||||
public DependencyNotSatisfiedException(Class<?> dependency) {
|
|
||||||
super(String.format("Dependency %s not satisfied", dependency.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package de.joshavg.simpledic.exception;
|
|
||||||
|
|
||||||
public class NoDefaultConstructorException extends RuntimeException {
|
|
||||||
public NoDefaultConstructorException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package de.joshavg.simpledic.exception;
|
|
||||||
|
|
||||||
public class SdicClassNotFoundException extends RuntimeException {
|
|
||||||
public SdicClassNotFoundException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,7 @@
|
|||||||
|
package de.joshavg.simpledic.exception;
|
||||||
|
|
||||||
|
public class SdicInstantiationException extends RuntimeException {
|
||||||
|
public SdicInstantiationException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package de.joshavg.simpledic.exception.integrity;
|
||||||
|
|
||||||
|
public class DependencyCycleDetected extends RuntimeException {
|
||||||
|
|
||||||
|
public DependencyCycleDetected(Class<?> clz) {
|
||||||
|
super(String.format("%s has a dependency cycle", clz));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package de.joshavg.simpledic.exception.integrity;
|
||||||
|
|
||||||
|
public class DependencyNotSatisfied extends RuntimeException {
|
||||||
|
public DependencyNotSatisfied(Class<?> dependency) {
|
||||||
|
super(String.format("Dependency %s not satisfied", dependency.getName()));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package de.joshavg.simpledic.exception.integrity;
|
||||||
|
|
||||||
|
public class DuplicatedServiceClassesFound extends RuntimeException {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package de.joshavg.simpledic.exception.integrity;
|
||||||
|
|
||||||
|
public class MoreThanOneConstructor extends RuntimeException {
|
||||||
|
|
||||||
|
public MoreThanOneConstructor(Class<?> clz) {
|
||||||
|
super(String.format("class %s has more than one constructor", clz));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package de.joshavg.simpledic.exception.integrity;
|
||||||
|
|
||||||
|
public class SdicClassNotFound extends RuntimeException {
|
||||||
|
public SdicClassNotFound(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package de.joshavg.simpledic;
|
||||||
|
|
||||||
|
import de.joshavg.simpledic.exception.ClassNotRegistered;
|
||||||
|
import de.joshavg.simpledic.exception.ContainerInitException;
|
||||||
|
import de.joshavg.simpledic.exception.SdicInstantiationException;
|
||||||
|
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.services.Depends1;
|
||||||
|
import de.joshavg.simpledic.services.PrivateConstructor;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ErrorTests {
|
||||||
|
|
||||||
|
@Test(expected = DuplicatedServiceClassesFound.class)
|
||||||
|
public void testDuplicationDetection() {
|
||||||
|
SdiContainer.load("duplicated.properties");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DependencyCycleDetected.class)
|
||||||
|
public void testCycleDetection() {
|
||||||
|
SdiContainer.load("cycle.properties").createInstance(Depends1.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ClassNotRegistered.class)
|
||||||
|
public void requestUnknownClass() {
|
||||||
|
SdiContainer.load("almostsane.properties").createInstance(Map.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DependencyNotSatisfied.class)
|
||||||
|
public void requestServiceWithUnregisteredDependency() {
|
||||||
|
SdiContainer.load("unknowndep.properties").createInstance(Depends1.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = SdicInstantiationException.class)
|
||||||
|
public void privateConstructor() {
|
||||||
|
SdiContainer.load("almostsane.properties").createInstance(PrivateConstructor.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = NullPointerException.class)
|
||||||
|
public void requestNull() {
|
||||||
|
SdiContainer.load("almostsane.properties").createInstance(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ContainerInitException.class)
|
||||||
|
public void loadUnknownFile() {
|
||||||
|
SdiContainer.load("unknown.properties");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package de.joshavg.simpledic;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class IntegrityCheckTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void servicesStartWithServiceDot() {
|
||||||
|
assertThat(IntegrityCheck.isServiceName("dingens"), is(false));
|
||||||
|
assertThat(IntegrityCheck.isServiceName("service"), is(false));
|
||||||
|
assertThat(IntegrityCheck.isServiceName("service."), is(false));
|
||||||
|
assertThat(IntegrityCheck.isServiceName("service.a"), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
package de.joshavg.simpledic;
|
|
||||||
|
|
||||||
public class NoDependencies {
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package de.joshavg.simpledic;
|
|
||||||
|
|
||||||
public class ServiceOne {
|
|
||||||
public ServiceOne(NoDependencies b) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,20 @@
|
|||||||
|
package de.joshavg.simpledic;
|
||||||
|
|
||||||
|
import de.joshavg.simpledic.services.DependsOnNoDependencies;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class SimpleDependenciesTest {
|
||||||
|
|
||||||
|
private static final String FILENAME = "almostsane.properties";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIntegrityCheck() {
|
||||||
|
SdiContainer.load(FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createOneDependencyService() {
|
||||||
|
SdiContainer container = SdiContainer.load(FILENAME);
|
||||||
|
container.createInstance(DependsOnNoDependencies.class);
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
package de.joshavg.simpledic;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class TestProperties {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIntegrityCheck() {
|
|
||||||
SdiContainer.load("test.properties");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,7 @@
|
|||||||
|
package de.joshavg.simpledic.services;
|
||||||
|
|
||||||
|
public class Depends1 {
|
||||||
|
|
||||||
|
public Depends1(Depends2 d) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package de.joshavg.simpledic.services;
|
||||||
|
|
||||||
|
public class Depends2 {
|
||||||
|
|
||||||
|
public Depends2(Depends3 d) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package de.joshavg.simpledic.services;
|
||||||
|
|
||||||
|
public class Depends3 {
|
||||||
|
|
||||||
|
public Depends3(Depends1 d) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package de.joshavg.simpledic.services;
|
||||||
|
|
||||||
|
public class DependsOnNoDependencies {
|
||||||
|
public DependsOnNoDependencies(NoDependencies b) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package de.joshavg.simpledic.services;
|
||||||
|
|
||||||
|
public class NoDependencies {
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package de.joshavg.simpledic.services;
|
||||||
|
|
||||||
|
public class PrivateConstructor {
|
||||||
|
|
||||||
|
private PrivateConstructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
service.one: de.joshavg.simpledic.services.DependsOnNoDependencies
|
||||||
|
service.nodeps: de.joshavg.simpledic.services.NoDependencies
|
||||||
|
service.private: de.joshavg.simpledic.services.PrivateConstructor
|
@ -0,0 +1,3 @@
|
|||||||
|
service.dep1:de.joshavg.simpledic.services.Depends1
|
||||||
|
service.dep2:de.joshavg.simpledic.services.Depends2
|
||||||
|
service.dep3:de.joshavg.simpledic.services.Depends3
|
@ -0,0 +1,3 @@
|
|||||||
|
service.one:de.joshavg.simpledic.services.DependsOnNoDependencies
|
||||||
|
service.nodeps:de.joshavg.simpledic.services.NoDependencies
|
||||||
|
service.nodeps2:de.joshavg.simpledic.services.NoDependencies
|
@ -1,2 +0,0 @@
|
|||||||
service.one: de.joshavg.simpledic.ServiceOne
|
|
||||||
service.nodeps: de.joshavg.simpledic.NoDependencies
|
|
@ -0,0 +1 @@
|
|||||||
|
service.dep1: de.joshavg.simpledic.services.Depends1
|
Loading…
Reference in new issue