diff --git a/OWNER.md b/OWNER.md
new file mode 100644
index 0000000..131e98d
--- /dev/null
+++ b/OWNER.md
@@ -0,0 +1 @@
+Podoltseva Olga
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 00b166b..b079735 100644
--- a/pom.xml
+++ b/pom.xml
@@ -84,6 +84,11 @@
slf4j-log4j12
1.7.5
+
+ org.jdom
+ jdom2
+ 2.0.5
+
diff --git a/src/main/java/arhangel/dim/container/Bean.java b/src/main/java/arhangel/dim/container/Bean.java
index 34b8f1e..6da148a 100644
--- a/src/main/java/arhangel/dim/container/Bean.java
+++ b/src/main/java/arhangel/dim/container/Bean.java
@@ -7,7 +7,7 @@
*/
public class Bean {
- private String name; // Уникально имя бина
+ private String name; // Уникальное имя бина
private String className; // Класс бина
private Map properties; // Набор полей бина ИмяПоля-Значение
diff --git a/src/main/java/arhangel/dim/container/BeanGraph.java b/src/main/java/arhangel/dim/container/BeanGraph.java
index 306c0e6..480efea 100644
--- a/src/main/java/arhangel/dim/container/BeanGraph.java
+++ b/src/main/java/arhangel/dim/container/BeanGraph.java
@@ -1,8 +1,10 @@
package arhangel.dim.container;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
*
@@ -16,7 +18,12 @@ public class BeanGraph {
* @param value - объект, привязанный к вершине
*/
public BeanVertex addVertex(Bean value) {
- return null;
+ if (value == null) {
+ throw new NullPointerException("null value");
+ }
+ BeanVertex newVertex = new BeanVertex(value);
+ vertices.put(newVertex, new ArrayList<>());
+ return newVertex;
}
/**
@@ -24,27 +31,70 @@ public BeanVertex addVertex(Bean value) {
* @param from из какой вершины
* @param to в какую вершину
*/
- public void addEdge(BeanVertex from ,BeanVertex to) {
+ public void addEdge(BeanVertex from, BeanVertex to) {
+ if (from == null || to == null) {
+ throw new NullPointerException("null vertex");
+ }
+ if (!isConnected(from, to)) {
+ vertices.get(from).add(to);
+ }
}
/**
* Проверяем, связаны ли вершины
*/
public boolean isConnected(BeanVertex v1, BeanVertex v2) {
- return false;
+ return getLinked(v1).contains(v2);
}
/**
* Получить список вершин, с которыми связана vertex
*/
public List getLinked(BeanVertex vertex) {
- return null;
+ return vertices.get(vertex);
}
/**
* Количество вершин в графе
*/
public int size() {
- return 0;
+ return vertices.size();
}
+
+ /**
+ * Топологическая сортировка
+ * Предполагаем, что все верщины до начала сортировки белые
+ * @return null, если в графе есть цикл, иначе массив отсортированных вершин
+ */
+ public List topSort() {
+ List sortedList = new ArrayList<>();
+ Set vertexSet = vertices.keySet();
+ for (BeanVertex vertex : vertexSet) {
+ if (!dfs(vertex, sortedList)) {
+ return null;
+ }
+ }
+ return sortedList;
+ }
+
+ /**
+ * Обход дерева в глубину, с проверкой на цикличность
+ * @param vertex вершина, с которой начинаем обход
+ * @return true, если все успешно, false, если встретился цикл
+ */
+ public boolean dfs(BeanVertex vertex, List sortedList) {
+ vertex.setColor(BeanVertex.Color.GRAY);
+ for (BeanVertex v : getLinked(vertex)) {
+ if (v.getColor() == BeanVertex.Color.GRAY) {
+ return false;
+ }
+ if (v.getColor() == BeanVertex.Color.WHITE) {
+ dfs(v, sortedList);
+ }
+ }
+ vertex.setColor(BeanVertex.Color.BLACK);
+ sortedList.add(vertex);
+ return true;
+ }
+
}
diff --git a/src/main/java/arhangel/dim/container/BeanVertex.java b/src/main/java/arhangel/dim/container/BeanVertex.java
index 1fbd084..abaa367 100644
--- a/src/main/java/arhangel/dim/container/BeanVertex.java
+++ b/src/main/java/arhangel/dim/container/BeanVertex.java
@@ -4,10 +4,19 @@
* Вершина графа, которая содержит бин
*/
public class BeanVertex {
+
+ public enum Color {
+ WHITE,
+ GRAY,
+ BLACK
+ }
+
private Bean bean;
+ private Color color;
public BeanVertex(Bean bean) {
this.bean = bean;
+ this.color = Color.WHITE;
}
public Bean getBean() {
@@ -17,4 +26,12 @@ public Bean getBean() {
public void setBean(Bean bean) {
this.bean = bean;
}
+
+ public void setColor(Color color) {
+ this.color = color;
+ }
+
+ public Color getColor() {
+ return color;
+ }
}
diff --git a/src/main/java/arhangel/dim/container/BeanXmlReader.java b/src/main/java/arhangel/dim/container/BeanXmlReader.java
index 3350f1e..d8483f6 100644
--- a/src/main/java/arhangel/dim/container/BeanXmlReader.java
+++ b/src/main/java/arhangel/dim/container/BeanXmlReader.java
@@ -1,10 +1,19 @@
package arhangel.dim.container;
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+
-/**
- *
- */
public class BeanXmlReader {
private static final String TAG_BEAN = "bean";
private static final String TAG_PROPERTY = "property";
@@ -14,8 +23,52 @@ public class BeanXmlReader {
private static final String ATTR_BEAN_ID = "id";
private static final String ATTR_BEAN_CLASS = "class";
- public List parseBeans(String pathToFile) {
- return null;
+ public List parseBeans(String pathToFile) throws IOException, JDOMException, InvalidConfigurationException {
+ SAXBuilder saxBuilder = new SAXBuilder();
+ File inputFile = new File(pathToFile);
+ if (!inputFile.exists()) {
+ throw new FileNotFoundException("File " + pathToFile + " doesn't exist");
+ }
+ Document document = saxBuilder.build(inputFile);
+ Element rootElement = document.getRootElement();
+ List beanList = rootElement.getChildren();
+ List classList = new ArrayList<>();
+ for (Element bean : beanList) {
+ if (!bean.getName().equals(TAG_BEAN)) {
+ throw new InvalidConfigurationException("Incorrect tag: " + bean.getName());
+ }
+ if (bean.getAttributeValue(ATTR_BEAN_CLASS) == null || bean.getAttributeValue(ATTR_BEAN_ID) == null) {
+ throw new InvalidConfigurationException("Bean should have both class and id attributes");
+ }
+ if (classList.contains(bean.getAttributeValue(ATTR_BEAN_ID))) {
+ throw new InvalidConfigurationException("All beans should have unique ids");
+ }
+ List propertyList = bean.getChildren();
+ Map propertyMap = new HashMap<>();
+ for (Element prop : propertyList) {
+ if (!prop.getName().equals(TAG_PROPERTY)) {
+ throw new InvalidConfigurationException("Incorrect property: " + prop.getName());
+ }
+ if (prop.getAttributes().size() > 2) {
+ throw new InvalidConfigurationException("Property should have only 2 attributes: " +
+ ATTR_NAME + " and " + ATTR_REF + " or " + ATTR_VALUE);
+ }
+ String name = prop.getAttributeValue(ATTR_NAME);
+ if (name == null) {
+ throw new InvalidConfigurationException("Incorrect property name");
+ }
+ if (prop.getAttributeValue(ATTR_VALUE) != null) {
+ propertyMap.put(name, new Property(name, prop.getAttributeValue(ATTR_VALUE), ValueType.VAL));
+ } else if (prop.getAttributeValue(ATTR_REF) != null) {
+ propertyMap.put(name, new Property(name, prop.getAttributeValue(ATTR_REF), ValueType.REF));
+ } else {
+ throw new InvalidConfigurationException("Incorrect property attribute. " + name +
+ " should have " + ATTR_REF + " or " + ATTR_NAME);
+ }
+ }
+ classList.add(new Bean(bean.getAttributeValue(ATTR_BEAN_ID),
+ bean.getAttributeValue(ATTR_BEAN_CLASS), propertyMap));
+ }
+ return classList;
}
-
}
diff --git a/src/main/java/arhangel/dim/container/Container.java b/src/main/java/arhangel/dim/container/Container.java
index 4213396..2d018fa 100644
--- a/src/main/java/arhangel/dim/container/Container.java
+++ b/src/main/java/arhangel/dim/container/Container.java
@@ -1,20 +1,42 @@
package arhangel.dim.container;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Используйте ваш xml reader чтобы прочитать конфиг и получить список бинов
*/
public class Container {
private List beans;
+ private Map objectsByName = new HashMap<>();
+ private Map objectsByClass = new HashMap<>();
/**
* Если не получается считать конфиг, то бросьте исключение
* @throws InvalidConfigurationException неверный конфиг
*/
public Container(String pathToConfig) throws InvalidConfigurationException {
-
- // вызываем BeanXmlReader
+ BeanXmlReader xmlReader = new BeanXmlReader();
+ try {
+ beans = xmlReader.parseBeans(pathToConfig);
+ } catch (Exception ex) {
+ throw new InvalidConfigurationException(ex.getMessage());
+ }
+
+ List sortVertices = beanSort();
+ if (sortVertices == null) {
+ throw new InvalidConfigurationException("Beans cycled");
+ }
+ try {
+ for (BeanVertex v : sortVertices) {
+ instantiateBean(v.getBean());
+ }
+ } catch (Exception ex) {
+ throw new InvalidConfigurationException(ex.getMessage());
+ }
}
/**
@@ -22,7 +44,7 @@ public Container(String pathToConfig) throws InvalidConfigurationException {
* Например, Car car = (Car) container.getByName("carBean")
*/
public Object getByName(String name) {
- return null;
+ return objectsByName.get(name);
}
/**
@@ -30,35 +52,90 @@ public Object getByName(String name) {
* Например, Car car = (Car) container.getByClass("arhangel.dim.container.Car")
*/
public Object getByClass(String className) {
- return null;
+ return objectsByClass.get(className);
}
- private void instantiateBean(Bean bean) {
-
- /*
- // Примерный ход работы
+ private List beanSort() throws InvalidConfigurationException {
+ BeanGraph graph = new BeanGraph();
+ Map vertexByName = new HashMap<>();
+ for (Bean bean : beans) {
+ vertexByName.put(bean.getName(), graph.addVertex(bean));
+ }
+ for (Bean bean : beans) {
+ for (Map.Entry prop : bean.getProperties().entrySet()) {
+ if (prop.getValue().getType() == ValueType.REF) {
+ BeanVertex from = vertexByName.get(bean.getName());
+ BeanVertex to = vertexByName.get(prop.getValue().getValue());
+ if (to == null) {
+ throw new InvalidConfigurationException("WTF?");
+ }
+ graph.addEdge(from, to);
+ }
+ }
+ }
+ return graph.topSort();
+ }
+ private void instantiateBean(Bean bean) throws Exception {
String className = bean.getClassName();
Class clazz = Class.forName(className);
// ищем дефолтный конструктор
Object ob = clazz.newInstance();
-
-
for (String name : bean.getProperties().keySet()) {
// ищем поле с таким именен внутри класса
// учитывая приватные
Field field = clazz.getDeclaredField(name);
// проверяем, если такого поля нет, то кидаем InvalidConfigurationException с описание ошибки
-
+ if (field == null) {
+ throw new InvalidConfigurationException("Class " + className + " doesn't have field " + name);
+ }
// Делаем приватные поля доступными
field.setAccessible(true);
+ Property prop = bean.getProperties().get(name);
+ Class> type = field.getType();
+ Method setter = clazz.getDeclaredMethod("set" + Character.toUpperCase(name.charAt(0)) + name.substring(1),
+ type);
+ if (setter == null) {
+ throw new InvalidConfigurationException("Class " + className + " doesn't have setter for field " + name);
+ }
+ switch (prop.getType()) {
+ case VAL:
+ //field.set(ob, convert(type.getTypeName(), prop.getValue()));
+ setter.invoke(ob, convert(type.getTypeName(), prop.getValue()));
+ break;
+ case REF:
+ String refName = prop.getValue();
+ if (objectsByName.containsKey(refName)) {
+ //field.set(ob, objectsByName.get(refName));
+ setter.invoke(ob, objectsByName.get(refName));
+ } else {
+ throw new InvalidConfigurationException("Failed to instantiate bean. Field " + name);
+ }
+ break;
+ default:
+ }
+ objectsByName.put(bean.getName(), ob);
+ objectsByClass.put(bean.getClassName(), ob);
+ }
- // Далее определяем тип поля и заполняем его
- // Если поле - примитив, то все просто
- // Если поле ссылка, то эта ссылка должа была быть инициализирована ранее
-
- */
+ }
+ // конвертирует строку в объект соответствующего типа
+ private Object convert(String typeName, String data) throws Exception {
+ switch (typeName) {
+ case "int":
+ case "Integer":
+ return Integer.valueOf(data);
+ case "double":
+ case "Double":
+ return Double.valueOf(data);
+ case "boolean":
+ case "Boolean":
+ return Boolean.valueOf(data);
+ default:
+ throw new InvalidConfigurationException("type name = " + typeName);
+ }
}
+
}
diff --git a/src/main/java/arhangel/dim/container/TestMain.java b/src/main/java/arhangel/dim/container/TestMain.java
new file mode 100644
index 0000000..1813d3c
--- /dev/null
+++ b/src/main/java/arhangel/dim/container/TestMain.java
@@ -0,0 +1,15 @@
+package arhangel.dim.container;
+
+import arhangel.dim.container.beans.Car;
+
+public class TestMain {
+ public static void main(String[] args) {
+ try {
+ Container container = new Container("config.xml");
+ Car car = (Car) container.getByClass("arhangel.dim.container.beans.Car");
+ System.out.println(car.getEngine().getPower());
+ } catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+}