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