Skip to content

Commit

Permalink
Week 8 DI exercise solution
Browse files Browse the repository at this point in the history
  • Loading branch information
lamfalusy committed Oct 18, 2020
1 parent 7fcae0a commit 07ce27e
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 0 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ Mire kell figyelni ezen XML feldolgozása során, és miért? Milyen probléma a

## Nyolcadik hét

### DI
Implementálj egy alap DI (Dependency Injection) keretrendszert Java-ban annotációk és reflexió használatával megvalósítva az IoC-t (Inversion Of Control).

### JSON szerializáció
Implementálj egy JSON szerializációs könyvtárat, mely képes kezelni sztringeket, számokat, listákat és beágyazott objektumokat. A megoldás meg kell feleljen az összes adott unit tesztnek.

Plusz feladat:
* a könyvtár tudjon deszerializálni

### Exception handling
Adott az alábbi kódrészlet:
```
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<module>week-5/its-gone-or-is-it</module>
<module>week-6/refactoring</module>
<module>week-7/xml-parsing</module>
<module>week-8/dependency-injection-framework</module>
<module>week-8/exception-handling</module>
<module>week-8/json-serialization</module>
</modules>
Expand Down
16 changes: 16 additions & 0 deletions week-8/dependency-injection-framework/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.epam.training</groupId>
<artifactId>unideb-prog2-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../..</relativePath>
</parent>

<artifactId>dependency-injection-framework</artifactId>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.epam.training;

import com.epam.training.di.Configuration;
import com.epam.training.di.DiContext;
import com.epam.training.di.annotation.Bean;
import com.epam.training.di.annotation.Qualifier;
import com.epam.training.di.impl.DiContextBuilder;

public class Main {

public static void main(String[] args) {
DiContext context = new DiContextBuilder().build(new Main.ContextConfig());
System.out.println(context.getBean("stringBuilder", StringBuilder.class).get().toString());
}

public static class ContextConfig implements Configuration {

@Bean
public String stringBeanA() {
return "StringA";
}

@Bean
public String stringBeanB() {
return "StringB";
}

@Bean
public Integer integerBean() {
return 10;
}

@Bean
public StringBuilder stringBuilder(@Qualifier(name = "stringBeanA") String string, Integer integer) {
return new StringBuilder(string).append(integer);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.epam.training.di;

public interface Configuration {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.epam.training.di;

import java.util.Optional;

public interface DiContext {

public void addBean(String beanName, Object bean);

public <T> Optional<T> getBean(String beanName, Class<T> clazz);

public <T> Optional<T> getBean(Class<T> clazz);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.epam.training.di.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.epam.training.di.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Qualifier {

public String name();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.epam.training.di.impl;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;

import com.epam.training.di.Configuration;
import com.epam.training.di.DiContext;
import com.epam.training.di.annotation.Bean;
import com.epam.training.di.annotation.Qualifier;

public class DiContextBuilder {

public DiContext build(Configuration contextConfiguration) {
DiContext context = new DiContextImpl();

List<BeanDefinition> beanDefinitions = new LinkedList<BeanDefinition>();

for (Method method : contextConfiguration.getClass().getMethods()) {
if (isAnnotatedWithBean(method)) {
List<BeanDefinitionParameter> beanDependencies = new LinkedList<DiContextBuilder.BeanDefinitionParameter>();
for (Parameter parameter : method.getParameters()) {
String name = null;
if (parameter.getAnnotation(Qualifier.class) != null) {
name = parameter.getAnnotation(Qualifier.class).name();
}
beanDependencies.add(new BeanDefinitionParameter<>(name, parameter.getType()));
}
beanDefinitions.add(new BeanDefinition(method.getName(), method.getReturnType(), method, beanDependencies));
}
}

int beanDefinitionsSize = beanDefinitions.size();
context.addBean("context", context);

while (beanDefinitions.size() > 0) {
List<BeanDefinition> resolvedBeanDefinitions = new LinkedList<>();
for (BeanDefinition beanDefinition : beanDefinitions) {
if (isBeanDefinitionResolvable(beanDefinition, context)) {
context.addBean(beanDefinition.name, createBean(beanDefinition, context, contextConfiguration));
resolvedBeanDefinitions.add(beanDefinition);
}
}
beanDefinitions.removeAll(resolvedBeanDefinitions);

if (beanDefinitionsSize == beanDefinitions.size()) {
throw new IllegalArgumentException("Circular dependency!");
} else {
beanDefinitionsSize = beanDefinitions.size();
}
}

return context;
}

private Object createBean(BeanDefinition beanDefinition, DiContext context, Object contextConfiguration) {
List<Object> dependencies = new ArrayList<>(beanDefinition.dependencies.size());

for (BeanDefinitionParameter parameter : beanDefinition.dependencies) {
Optional<Object> dependency;
if (parameter.name != null) {
dependency = context.getBean(parameter.name, parameter.type);
} else {
dependency = context.getBean(parameter.type);
}
dependencies.add(dependency.get());
}

try {
return beanDefinition.builderMethod.invoke(contextConfiguration, dependencies.toArray());
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
}

private boolean isBeanDefinitionResolvable(BeanDefinition beanDefinition, DiContext context) {
for (BeanDefinitionParameter parameter : beanDefinition.dependencies) {
Optional<Object> dependency;
if (parameter.name != null) {
dependency = context.getBean(parameter.name, parameter.type);
} else {
dependency = context.getBean(parameter.type);
}
if (dependency.isEmpty()) {
return false;
}
}
return true;
}

private boolean isAnnotatedWithBean(Method method) {
for (Annotation annotation : method.getDeclaredAnnotations()) {
if (annotation.annotationType().equals(Bean.class)) {
return true;
}
}
return false;
}

private class BeanDefinition {
String name;
Class type;
Method builderMethod;
List<BeanDefinitionParameter> dependencies;

public BeanDefinition(String name, Class type, Method builderMethod,
List<BeanDefinitionParameter> dependencies) {
super();
this.name = name;
this.type = type;
this.builderMethod = builderMethod;
this.dependencies = dependencies;
}
}

private class BeanDefinitionParameter<T> {
String name;
Class<T> type;

public BeanDefinitionParameter(String name, Class<T> type) {
super();
this.name = name;
this.type = type;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.epam.training.di.impl;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.epam.training.di.DiContext;

public class DiContextImpl implements DiContext {

private Map<String, Object> context = new HashMap<>();
private Map<Class<?>, List<Object>> contextMappedByType = new HashMap<>();

public void addBean(String beanName, Object bean) {
if (context.containsKey(beanName)) {
throw new IllegalArgumentException("Bean has been already created: " + beanName);
}
context.put(beanName, bean);
if (contextMappedByType.containsKey(bean.getClass())) {
contextMappedByType.get(bean.getClass()).add(bean);
} else {
List<Object> objectList = new LinkedList<>();
objectList.add(bean);
contextMappedByType.put(bean.getClass(), objectList);
}
}

@SuppressWarnings("unchecked")
public <T> Optional<T> getBean(String beanName, Class<T> clazz) {
Optional<T> ret = null;
if (context.containsKey(beanName)) {
ret = Optional.of((T) context.get(beanName));
} else {
ret = Optional.empty();
}
return ret;
}

@SuppressWarnings("unchecked")
@Override
public <T> Optional<T> getBean(Class<T> clazz) {
Optional<T> ret = null;
List<Object> objestList = contextMappedByType.get(clazz);
if (objestList == null) {
ret = Optional.empty();
} else if (objestList.size() != 1) {
throw new IllegalArgumentException("There are multiple bean available for type: " + clazz);
} else {
ret = Optional.of((T) objestList.get(0));
}
return ret;
}

}

0 comments on commit 07ce27e

Please sign in to comment.