Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/main/java/arhangel/dim/container/Bean.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
* Представляет тег bean из конфига
*/
public class Bean {

private String name; // Уникально имя бина
private String className; // Класс бина
private Map<String, Property> properties; // Набор полей бина ИмяПоля-Значение
private Object object;

public Bean() {}

public Bean(String name, String className, Map<String, Property> properties) {
this.name = name;
Expand Down Expand Up @@ -49,4 +51,12 @@ public String toString() {
", properties=" + properties +
'}';
}

public Object getObject() {
return object;
}

public void setObject(Object object) {
this.object = object;
}
}
48 changes: 42 additions & 6 deletions src/main/java/arhangel/dim/container/BeanGraph.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package arhangel.dim.container;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -10,41 +11,76 @@
public class BeanGraph {
// Граф представлен в виде списка связности для каждой вершины
private Map<BeanVertex, List<BeanVertex>> vertices = new HashMap<>();
private List<BeanVertex> topSorted;

/**
* Добавить вершину в граф
* @param value - объект, привязанный к вершине
*/
public BeanVertex addVertex(Bean value) {
return null;
BeanVertex vertex = new BeanVertex(value);
vertices.put(vertex, new ArrayList<>());
return vertex;
}

/**
* Соединить вершины ребром
* @param from из какой вершины
* @param to в какую вершину
*/
public void addEdge(BeanVertex from ,BeanVertex to) {
public void addEdge(BeanVertex from, BeanVertex to) throws InvalidConfigurationException {
if (from == null || to == null || !vertices.containsKey(from)) {
throw new InvalidConfigurationException("Attempt to add invalid edge");
}
vertices.get(from).add(to);
}

/**
* Проверяем, связаны ли вершины
*/
public boolean isConnected(BeanVertex v1, BeanVertex v2) {
return false;
public boolean isConnected(BeanVertex from, BeanVertex to) throws InvalidConfigurationException {
if (from == null || to == null || !vertices.containsKey(from)) {
throw new InvalidConfigurationException("Attempt to check " +
" connection with nonexistent vertex");
}
return getLinked(from).contains(to);
}

/**
* Получить список вершин, с которыми связана vertex
*/
public List<BeanVertex> getLinked(BeanVertex vertex) {
return null;
return vertices.get(vertex);
}

/**
* Количество вершин в графе
*/
public int size() {
return 0;
return vertices.size();
}

private void dfs(BeanVertex vertex) throws CycleReferenceException {
vertex.color = BeanVertex.ColorType.GREY;
for (BeanVertex to : getLinked(vertex)) {
if (to.color == BeanVertex.ColorType.GREY) {
throw new CycleReferenceException();
}
if (to.color == BeanVertex.ColorType.BLACK) {
dfs(to);
}
}
vertex.color = BeanVertex.ColorType.WHITE;
topSorted.add(vertex);
}

public List<BeanVertex> topSort() throws CycleReferenceException {
topSorted = new ArrayList<>();
for (BeanVertex vertex : vertices.keySet()) {
if (vertex.color == BeanVertex.ColorType.BLACK) {
dfs(vertex);
}
}
return topSorted;
}
}
9 changes: 9 additions & 0 deletions src/main/java/arhangel/dim/container/BeanVertex.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@
public class BeanVertex {
private Bean bean;

enum ColorType {
BLACK,
GREY,
WHITE
}

ColorType color;

public BeanVertex(Bean bean) {
this.bean = bean;
this.color = ColorType.BLACK;
}

public Bean getBean() {
Expand Down
81 changes: 79 additions & 2 deletions src/main/java/arhangel/dim/container/BeanXmlReader.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
package arhangel.dim.container;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;

/**
*
Expand All @@ -14,8 +26,73 @@ public class BeanXmlReader {
private static final String ATTR_BEAN_ID = "id";
private static final String ATTR_BEAN_CLASS = "class";

public List<Bean> parseBeans(String pathToFile) {
return null;
public static List<Bean> read(String config) throws InvalidConfigurationException {
List<Bean> answer = new ArrayList<>();
Document document;
try {
document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File(config));
} catch (Exception e) {
throw new InvalidConfigurationException("Unable to read the xml");
}
NodeList root = document.getDocumentElement().getChildNodes();
Set<String> ids = new HashSet<>();
for (int i = 0; i < root.getLength(); ++i) {
Node node = root.item(i);
if (!TAG_BEAN.equals(node.getNodeName())) {
continue;
}
Bean currentBean = getBeanByNode(node);
answer.add(currentBean);
if (ids.contains(currentBean.getName())) {
throw new InvalidConfigurationException(String
.format("Duplicate name %s", currentBean.getName()));
}
ids.add(currentBean.getName());
}
return answer;
}

private static Bean getBeanByNode(Node node) throws InvalidConfigurationException {
Bean answer = new Bean();
NamedNodeMap attributes = node.getAttributes();
answer.setName(attributes.getNamedItem(ATTR_BEAN_ID).getNodeValue());
answer.setClassName(attributes.getNamedItem(ATTR_BEAN_CLASS).getNodeValue());
NodeList properties = node.getChildNodes();
Map<String, Property> answerProperties = new HashMap<>();
for (int i = 0; i < properties.getLength(); ++i) {
Node propertyNode = properties.item(i);
if (!TAG_PROPERTY.equals(propertyNode.getNodeName())) {
continue;
}
Property property = getPropertyByNode(propertyNode);
if (answerProperties.containsKey(property.getName())) {
throw new InvalidConfigurationException(String
.format("Duplicate property %s in bean %s",
property.getName(), answer.getName()));
}
answerProperties.put(property.getName(), property);
}
answer.setProperties(answerProperties);
return answer;
}

private static Property getPropertyByNode(Node node) throws InvalidConfigurationException {
Property answer = new Property();
NamedNodeMap attributes = node.getAttributes();
answer.setName(attributes.getNamedItem(ATTR_NAME).getNodeValue());
if (attributes.getNamedItem(ATTR_VALUE) != null) {
// value
answer.setType(ValueType.VAL);
answer.setValue(attributes.getNamedItem(ATTR_VALUE).getNodeValue());
} else {
if (attributes.getNamedItem(ATTR_REF) == null) {
throw new InvalidConfigurationException(String.format("Property %s is neither " +
"value nor reference", answer.getName()));
}
// reference
answer.setType(ValueType.REF);
answer.setValue(attributes.getNamedItem(ATTR_REF).getNodeValue());
}
return answer;
}
}
122 changes: 106 additions & 16 deletions src/main/java/arhangel/dim/container/Container.java
Original file line number Diff line number Diff line change
@@ -1,54 +1,107 @@
package arhangel.dim.container;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.ArrayList;
import java.util.Objects;

/**
* Используйте ваш xml reader чтобы прочитать конфиг и получить список бинов
*/
public class Container {
private List<Bean> beans;
private Map<String, Bean> beanById;
private Map<String, Bean> beanByClass;

/**
* Если не получается считать конфиг, то бросьте исключение
* @throws InvalidConfigurationException неверный конфиг
*/
public Container(String pathToConfig) throws InvalidConfigurationException {

// вызываем BeanXmlReader
String config = "";
Scanner scanner = new Scanner(pathToConfig);
while (scanner.hasNext()) {
config += scanner.nextLine() + "\n";
}
beans = BeanXmlReader.read(config);
for (Bean bean : beans) {
beanById.put(bean.getName(), bean);
beanByClass.put(bean.getClassName(), bean);
}

Map<String, BeanVertex> verticesByBeanNames = new HashMap<>();
BeanGraph graph = new BeanGraph();

for (Bean bean : beans) {
for (Property property : bean.getProperties().values()) {
if (property.getType() == ValueType.REF) {
graph.addEdge(verticesByBeanNames.get(bean.getName()),
verticesByBeanNames.get(property.getValue()));
}
}
}

try {
List<BeanVertex> sortedGraph = graph.topSort();
beans = new ArrayList<>();
for (BeanVertex vertex : sortedGraph) {
beans.add(vertex.getBean());
}
} catch (CycleReferenceException e) {
throw new InvalidConfigurationException("Cycle in references");
}

for (Bean bean : beans) {
instantiateBean(bean);
}
}

/**
* Вернуть объект по имени бина из конфига
* Например, Car car = (Car) container.getByName("carBean")
*/
public Object getByName(String name) {
return null;
if (!beanById.containsKey(name)) {
throw new UnsupportedOperationException(String
.format("No such bean: %s", name));
}
return beanById.get(name).getObject();
}

/**
* Вернуть объект по имени класса
* Например, Car car = (Car) container.getByClass("arhangel.dim.container.Car")
*/
public Object getByClass(String className) {
return null;
return beanByClass.get(className).getObject();
}

private void instantiateBean(Bean bean) {

/*
// Примерный ход работы

private void instantiateBean(Bean bean) throws InvalidConfigurationException {
String className = bean.getClassName();
Class clazz = Class.forName(className);
// ищем дефолтный конструктор
Object ob = clazz.newInstance();

Class clazz;
Object ob;
try {
clazz = Class.forName(className);
ob = clazz.newInstance();
} catch (Exception e) {
throw new InvalidConfigurationException(String
.format("Unable to create instance of class %s", className));
}

for (String name : bean.getProperties().keySet()) {
// ищем поле с таким именен внутри класса
// учитывая приватные
Field field = clazz.getDeclaredField(name);
// проверяем, если такого поля нет, то кидаем InvalidConfigurationException с описание ошибки
Field field;
try {
field = clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
throw new InvalidConfigurationException("No such field " + name);
}

// Делаем приватные поля доступными
field.setAccessible(true);
Expand All @@ -57,8 +110,45 @@ private void instantiateBean(Bean bean) {
// Если поле - примитив, то все просто
// Если поле ссылка, то эта ссылка должа была быть инициализирована ранее

*/
Method method;
String setterName = "set" + Character.toUpperCase(name.charAt(0)) +
name.substring(1);
try {
method = clazz.getDeclaredMethod(setterName, field.getType());
} catch (NoSuchMethodException e) {
throw new InvalidConfigurationException("No such method " + setterName);
}

Property prop = bean.getProperties().get(name);
Type type = field.getType();

try {
switch (prop.getType()) {
case VAL:
method.invoke(ob, objectByClassName(type.getTypeName(), prop.getValue()));
break;
case REF:
String refName = prop.getValue();
method.invoke(ob, getByName(refName));
break;
default:
}
} catch (Exception e) {
throw new InvalidConfigurationException(String
.format("Failed to instantiate field %s", name));
}
}
bean.setObject(ob);
}

private Object objectByClassName(String className, String value) {
if (Objects.equals(className, "int") || Objects.equals(className, "Integer")) {
return Integer.parseInt(value);
}
if (Objects.equals(className, "String")) {
return value;
}
throw new UnsupportedOperationException("convert to class " + className);
}

}
Loading