instantiation

integrity checks
master
Josha von Gizycki 8 years ago
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

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>de.joshavg</groupId> <groupId>de.joshavg</groupId>
@ -23,10 +23,28 @@
</build> </build>
<dependencies> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.12</version> <version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.collections</groupId> <groupId>com.google.collections</groupId>

@ -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;
}
}

@ -1,27 +1,27 @@
package de.joshavg.simpledic; package de.joshavg.simpledic;
import com.google.common.annotations.VisibleForTesting; import de.joshavg.simpledic.exception.ClassNotRegistered;
import de.joshavg.simpledic.exception.ContainerInitException; import de.joshavg.simpledic.exception.ContainerInitException;
import de.joshavg.simpledic.exception.DependencyNotSatisfiedException; import de.joshavg.simpledic.exception.SdicInstantiationException;
import de.joshavg.simpledic.exception.SdicClassNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SdiContainer { public class SdiContainer {
private static final Logger LOG = LoggerFactory.getLogger(SdiContainer.class);
private final Properties props; private final Properties props;
private final List<ServiceDefinition> definitions;
@VisibleForTesting private SdiContainer(Properties props, List<ServiceDefinition> definitions) {
SdiContainer(Properties props) {
this.props = props; this.props = props;
this.definitions = definitions;
} }
public static SdiContainer load() { public static SdiContainer load() {
@ -32,74 +32,40 @@ public class SdiContainer {
Properties props = new Properties(); Properties props = new Properties();
InputStream inputStream = SdiContainer.class.getClassLoader().getResourceAsStream(filename); InputStream inputStream = SdiContainer.class.getClassLoader().getResourceAsStream(filename);
if (inputStream == null) {
throw new ContainerInitException("config file " + filename + " not found", null);
}
try { try {
props.load(inputStream); props.load(inputStream);
} catch (IOException e) { } catch (IOException e) {
throw new ContainerInitException("failed loading properties", e); throw new ContainerInitException("failed loading properties", e);
} }
SdiContainer container = new SdiContainer(props); IntegrityCheck integrityCheck = new IntegrityCheck(props);
container.integrityCheck(); integrityCheck.check();
return container; return new SdiContainer(props, integrityCheck.getDefinitions());
} }
private void integrityCheck() { private List<Class<?>> serviceClasses() {
List<Class<?>> services = fetchAllServices(); return definitions.stream().map(ServiceDefinition::getClz).collect(Collectors.toList());
checkConstructorDependencies(services);
} }
private List<Class<?>> fetchAllServices() { public <T> T createInstance(Class<T> clz) {
return props.entrySet().stream() if (clz == null) {
.filter(e -> isServiceName(e.getKey().toString())) throw new NullPointerException();
.map(e -> e.getValue().toString()) }
.map(n -> {
try {
return Class.forName(n);
} catch (ClassNotFoundException e) {
throw new SdicClassNotFoundException(e);
}
}).collect(Collectors.toList());
}
private void checkConstructorDependencies(List<Class<?>> services) { LOG.trace("instance ordered: ", clz);
for(Class<?> c : services) { if (!serviceClasses().contains(clz)) {
Constructor<?>[] constructors = c.getDeclaredConstructors(); throw new ClassNotRegistered(clz);
for(Constructor constructor : constructors) {
Class[] parameterTypes = constructor.getParameterTypes();
System.out.println(Arrays.toString(parameterTypes));
System.out.println(Arrays.asList(parameterTypes));
}
} }
/* services.stream()
// get all constructors
.map(Class::getDeclaredConstructors)
.reduce(new ArrayList<Constructor>(),
(l, arr) -> {
l.addAll(Arrays.asList(arr));
return l;
},
(l1, l2) -> l1)
// get all parameter types
.stream()
.map(Constructor::getParameterTypes)
.reduce(new ArrayList<Class<?>>(),
(l, arr) -> {
l.addAll(Arrays.asList(arr));
return l;
},
(l1, l2) -> l1)
// search for services with needed types
.stream()
.distinct()
.forEach(t -> {
if (!services.contains(t)) {
throw new DependencyNotSatisfiedException(t);
}
});*/
}
@VisibleForTesting try {
static boolean isServiceName(String name) { return new Instantiator<>(clz).createInstance();
return name.startsWith("service."); } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw new SdicInstantiationException(e);
}
} }
} }

@ -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…
Cancel
Save