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()); + } + } +}