{"id":21565232,"url":"https://github.com/mastercloudapps-projects/umldiagramslib","last_synced_at":"2025-06-17T18:36:11.532Z","repository":{"id":53022173,"uuid":"278868745","full_name":"MasterCloudApps-Projects/UMLDiagramsLib","owner":"MasterCloudApps-Projects","description":null,"archived":false,"fork":false,"pushed_at":"2021-04-09T22:12:56.000Z","size":2011,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-18T05:17:40.403Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MasterCloudApps-Projects.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-11T13:38:27.000Z","updated_at":"2021-12-24T03:56:56.000Z","dependencies_parsed_at":"2022-09-08T15:51:02.458Z","dependency_job_id":null,"html_url":"https://github.com/MasterCloudApps-Projects/UMLDiagramsLib","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/MasterCloudApps-Projects/UMLDiagramsLib","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MasterCloudApps-Projects%2FUMLDiagramsLib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MasterCloudApps-Projects%2FUMLDiagramsLib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MasterCloudApps-Projects%2FUMLDiagramsLib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MasterCloudApps-Projects%2FUMLDiagramsLib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MasterCloudApps-Projects","download_url":"https://codeload.github.com/MasterCloudApps-Projects/UMLDiagramsLib/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MasterCloudApps-Projects%2FUMLDiagramsLib/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260419568,"owners_count":23006309,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-24T10:18:56.965Z","updated_at":"2025-06-17T18:36:06.522Z","avatar_url":"https://github.com/MasterCloudApps-Projects.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Generate UML\n\n## Descripción del proyecto\n1. Introducción\n2. Creación de un modelo de dominio\n3. Generar documentación\n4. Ingeniería inversa\n\n---\n### Introducción\nEste trabajo surge con la idea de poder agilizar la creación de documentación UML para el diseño arquitectónico de aplicaciones. Así como el análisis de su evolución.\n\nActualmente una de las maneras de generar documentación UML es mediante la herramienta de código abierto PlantUML, que consiste en un lenguaje de texto sin formato que interpretado por el servidor de PlantUML, nos genera el diagrama UML que hayamos creado con este lenguaje.\n\nEl crear documentación UML mediante este sistema es costoso en tiempo, ya que modelar la arquitectura de una aplicación de cierto tamaño requiere de mucho tiempo y en esfuerzo pues es sencillo cometer errores de sintaxis al generar el modelo de dominio en este lenguaje.\n\nEl objetivo de este TFM ha sido crear un prototipo de herramienta que nos permita generar documentación UML basándonos en el lenguaje de PlantUML.\n\nPara ello se ha optado por facilitar la generación de documentación sobre proyectos ya creados, mediante un analizador de código, que con ingeniería inversa hace una análisis de las clases existentes y sus relaciones. Este código puede estar alojado en nuestra máquina, o podemos descargarlo de un repositorio de Git para su análisis.\n\nSe ha desarrollado también una forma para la creación de nuevos modelos de dominio de manera programática, facilitando así su creación y reduciendo los posibles errores.\n\nTanto si el modelo del dominio lo hemos obtenido analizando un código ya existente, como si lo hemos creado nosotros, para poder obtener la documentación UML, primero convertiremos el modelo del dominio en lenguaje PlantUML.\n\t\nUna vez tenemos nuestro modelo de dominio convertido a lenguaje PlantUML, haremos una petición a los servidores de PlantUML que convertirá la información enviada en lenguaje PlantUML a una imagen y se guardará en nuestra máquina.\n\n\n\n\nPodemos resumir la funcionalidad de la herramienta de esta manera.\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram activity.svg)\n\n\nAquí vemos las conexiones que puede realizar la herramienta con los servidores de PlantUML, para la generación de los gráficos, y un repositorio de Git para la descarga del proyecto.\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram all nodes.svg)\n\nDiagrama de la arquitectura de la aplicación\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram application.svg)\n\n---\n### Creación de un modelo de dominio\n\nPara crear el modelo de dominio se ha decidido implementar un patrón bulider especial con pila de tipo unidades, de esta manera añadimos información sobre la última unidad apilada. Esto nos permite tener un grafo con las relaciones de las unidades del modelo del dominio.\n\nLa clase sobre la que iremos creando el modelo del dominio es la clase Dominio y usando los diferentes métodos de los que dispone, podremos ir añadiendo unidades al modelo, o funciones y/o atributos a la unidad activa.\n\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram model.svg)\n\nPara poder añadir correctamente la información al dominio tendremos un elemento activo (el último apilado) que será sobre el que iremos añadiendo la información. Este puede ser de tipo unidad, atributo o función.\n\n```java\ndomain.addPackage(\"package\")\n                .addUnit(\"Unit1\")\n                .setAbstractUnit()\n                .addUnit(\"Unit2\");\n```\n![Class Diagram domain model](src/main/resources/images/documentation/diagrama 1.svg)\n\nA la última unidad apilada le podemos añadir mediante estos métodos, unidades, funciones o atributos.\n\n##### addBase\n\n```java\ndomain.addPackage(\"package\")\n                .addUnit(\"Unit1\")\n                .addBase(\"Base\");\n                \n```\n![Class Diagram domain model](src/main/resources/images/documentation/diagrama base.svg)\n\n##### addPart\n\n```java\ndomain.addPackage(\"package\")\n                .addUnit(\"Unit1\")\n                .addPart(\"Part\");\n```\n![Class Diagram domain model](src/main/resources/images/documentation/diagrama part.svg)\n\n##### addElement\n```java\ndomain.addPackage(\"package\")\n                .addUnit(\"Unit1\")\n                .addElement(\"Element\");\n```\n![Class Diagram domain model](src/main/resources/images/documentation/diagrama element.svg)\n\n\n##### addAssociate\n```java\ndomain.addPackage(\"package\")\n                .addUnit(\"Unit1\")\n                .addAssociate(\"Associate\");\n```\n![Class Diagram domain model](src/main/resources/images/documentation/diagrama associate.svg)\n\n\n\n##### addUsed\n```java\ndomain.addPackage(\"package\")\n                .addUnit(\"Unit1\")\n                .addUsed(\"Used\");\n```\n![Class Diagram domain model](src/main/resources/images/documentation/diagram used.svg)\n\n\n\nAhora vamos a ver un ejemplo usando todos a la vez usando como referencia la práctica 1 de la asignatura de diseño y calidad del software del máster cloud apps.\n```java\ndomain.addPackage(\"mastermind\")\n            .addUnit(\"Mastermind\")\n                .addBase(\"WithConsoleModel\")\n                .addPart(\"SecretCombination\")\n                .addPart(\"ProposedCombination\")\n                .addPart(\"Result\")\n                .addUsed(\"Message\")               \n            .addUnit(\"Combination\")\n                .addBase(\"WithConsoleModel\")\n                .addPart(\"Color\")\n                .addPart(\"SecretCombination\")\n            .addUnit(\"SecretCombination\")\n                .addBase(\"Combination\")\n                .addUsed(\"ProposedCombination\")\n                .addUsed(\"Message\")\n                .addUsed(\"Result\")                \n            .addUnit(\"ProposedCombination\")\n                .addBase(\"Combination\")\n                .addUsed(\"Error\")\n                .addUsed(\"Message\");\n```\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram exercise 1 design.svg)\n\n\nAdemás de los métodos que hemos visto, también podemos añadir a una unidad atributos o funciones.\n\nAtributos\n```java\ndomain.addUnit(\"unit\")\n                .addAttribute(\"attribute\")\n                .setType(\"String\")\n                .addVisibility(Visibility.PUBLIC)\n                .setStatic(true);\n```\n![Class Diagram domain model](src/main/resources/images/documentation/diagram attribute.svg)\n\n\nFunciones\n```java\nString[] parameters = {\"String\", \"int\"};\ndomain.addUnit(\"unit\")\n.addFunction(\"function\")\n.addVisibility(Visibility.PUBLIC)\n.addReturnType(\"String\")\n               .addParameters(parameters);\n```\n![Class Diagram domain model](src/main/resources/images/documentation/diagram function.svg)\n\n\nY la combinación de ambos\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram attribute and function.svg)\n\n#### Análisis técnico de los métodos más relevantes\n\n##### Domain\n\n###### getUnit\n\nTenemos 3 métodos que nos permiten obtener una unidad. Por su nombre, por su nombre y su paquete o buscando la propia unidad\n````java\nprivate Unit getUnit(Unit unit) {\n    Unit aux = getUnit(unit.name, unit.getMyPackage());\n    if (aux == null) {\n        if (StringUtils.isEmpty(unit.getMyPackage()))\n            unit.setMyPackage(this.activePackage);\n        unitList.add(unit);\n        aux = unit;\n    }\n    return aux;\n}\n\npublic Unit getUnit(String name, String packageDescription) {\n    LOG.debug(\"get unit {} with package {}\", name, packageDescription);\n    Stream\u003cUnit\u003e stream = unitList.stream().filter(e -\u003e e.name.equals(name));\n    if(packageDescription != null){\n        return stream.filter(e -\u003e StringUtils.equals(e.getMyPackage(),packageDescription))\n                .findFirst().orElse(null);\n    }\n    else\n        return stream.findFirst().orElse(null);\n}\n\npublic Unit getUnit(String name){\n    return getUnit(name, null);\n}\n````\nAl final el método que realmente realiza la busqueda es el que buscamos por su nombre y paquete.\nEn él creamos un strem que filtra buscando en todas las unidades y busca que el nombre de la unidad sea el mismo que estamos buscando.\n```java\nStream\u003cUnit\u003e stream = unitList.stream().filter(e -\u003e e.name.equals(name));\n```\nDespués si el paquete existe, hacemos otro filtro en el stream por el nombre del paquete y devolvemos el primer valor obtenido.\n```java\nreturn stream.filter(e -\u003e StringUtils.equals(e.getMyPackage(),packageDescription))\n                .findFirst().orElse(null);\n```\nEn caso de que el nombre del paquete sea nulo, devolvemos el primer valor del stream sin realiar el filtrado anterior.\n````java\nreturn stream.findFirst().orElse(null);\n````\n\n###### getAllEfferents\nPara obtener el grafo de acoplamiento eferente usamos como apoyo el método **cloneForEfferent**\n````java\npublic List\u003cUnit\u003e getAllEfferents() {\n    LOG.debug(\"get all efferents\");\n    List\u003cUnit\u003e list = new ArrayList\u003c\u003e();\n    unitList.forEach(unit -\u003e {\n        try {\n            list.add(cloneForEfferent(unit, \"efferent \" + unit.name));\n        } catch (IOException | ClassNotFoundException e) {\n            LOG.debug(\"context\",e);\n        }\n\n    });\n    return list;\n}\n````\nEn este método, usamos el método **makeClone** de la clase unit para clonar la unidad que recibimos por parámetro y no modificar el grafo de relaciones que tenemos creado.\n````java\nprivate Unit cloneForEfferent(Unit unit, String packageDescription) throws IOException, ClassNotFoundException {\n    Unit newUnit = unit.makeClone();\n    newUnit.getBase().forEach(b -\u003e b.setMyPackage(packageDescription));\n    newUnit.getPartList().forEach(p -\u003e p.setMyPackage(packageDescription));\n    newUnit.getElements().forEach(e -\u003e e.setMyPackage(packageDescription));\n    newUnit.getAssociates().forEach(a -\u003e a.setMyPackage(packageDescription));\n    newUnit.getUsed().forEach(u -\u003e u.setMyPackage(packageDescription));\n    newUnit.setMyPackage(packageDescription);\n    return newUnit;\n}\n````\n\nUna vez hecho esto, le añadimos a todas la unidades con las que tiene una relación y a ella misma, el paquete para poder representar caga grafo de acoplamiento en su paquete.\n\n###### getAllAfferent\nEste método nos provee un grafo con el acoplamiento aferente de todas las unidades del modelo del dominio. Para ello se basa en el método **getEferent** al cual le vamos pasando cada una de las unidades que componen nuestro modelo del dominio.\n````java\npublic List\u003cUnit\u003e getAllAfferent() {\n    List\u003cUnit\u003e allAfferent = new ArrayList\u003c\u003e();\n    unitList.forEach(u -\u003e allAfferent.addAll(getAfferent(u.name, \"afferent \" + u.name)));\n    return allAfferent;\n}\n\nprivate List\u003cUnit\u003e getAfferent(String unitName, String packageDescription) {\n    LOG.debug(\"get afferent with package:{}-{}\", unitName, packageDescription);\n    return findAfferent(new Unit(unitName, packageDescription), true);\n}\n````\nEl método **getAfferent** usa el método **findAfferent** que es el encargado de buscar el acoplamiento aferente de la unidad que le pasamos.\n```java\nprivate List\u003cUnit\u003e findAfferent(Unit unit, boolean clone) {\n    List\u003cUnit\u003e afferent = new ArrayList\u003c\u003e();\n    unitList.forEach(u -\u003e {\n        Unit afferentUnit = createUnit(unit, clone, u);\n        boolean find = false;\n        if (u.getBase().contains(unit)) {\n            afferentUnit.addBase(unit);\n            find = true;\n        }\n        if (u.getPartList().contains(unit)) {\n            afferentUnit.addPart(unit);\n            find = true;\n        }\n        if (u.getAssociates().contains(unit)) {\n            afferentUnit.addAssociate(unit);\n            find = true;\n        }\n        if (u.getElements().contains(unit)) {\n            afferentUnit.addElement(unit);\n            find = true;\n        }\n        if (u.getUsed().contains(unit)) {\n            afferentUnit.addUsed(unit);\n            find = true;\n        }\n        if (find)\n            afferent.add(afferentUnit);\n    });\n    return afferent;\n}\n```\nPara mostrar el grafo de acopalimento, al igual que antes, modificamos el paquete para que cada unidad tenga su grafo de acoplamiento definido en un paquete, para el método llama al método **createUnit** para que nos devuelva una nueva unidad con el paquete definido.\n```java\nprivate Unit createUnit(Unit unit, boolean clone, Unit u) {\n    return clone ? new Unit(u.name, unit.getMyPackage()):new Unit(u.name);\n}\n```\n##### Unit\n\n###### makeClone\nEn esta clase vamos a empezar por el método **makeClone** que hemos hecho referencia en el apartado de la clase Domain.\n\n```java\npublic Unit makeClone() throws IOException, ClassNotFoundException {\n    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();\n    ObjectOutputStream out = new ObjectOutputStream(outputStream);\n    out.writeObject(this);\n    \n    ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());\n    ObjectInputStream in = new ObjectInputStream(inputStream);\n    return (Unit) in.readObject();\n}\n```\nCon este método lo que hacemos es serializar y deserializar el objeto para tener un nuevo objeto \"clonado\" manteniendo las relaciones que posee con otras entidades.\n\n###### toStringClassFormat\nEste método será el encargado de transformar las relaciones que posee la entidad en lenguaje PlantUML haciendo uso de los métodos proporcionados por las clases Attribue y Function\n````java\npublic String toStringClassFormat(){\n    StringBuilder stringBuilder = new StringBuilder();\n    if(abstractUnit)\n        stringBuilder.append(ABSTRACT_AND_SPACE);\n    stringBuilder.append(CLASS_AND_SPACE);\n    stringBuilder.append(printName());\n    if(containsAttributeOrFunction()){\n        stringBuilder.append(\"{\" + LINE_BREAK);\n        this.attributes.forEach(attribute -\u003e stringBuilder.append(attribute.toString()));\n        stringBuilder.append(LINE_BREAK);\n        this.functions.forEach(function -\u003e stringBuilder.append(function.toString()));\n        stringBuilder.append(LINE_BREAK + \"}\");\n    }\n    stringBuilder.append(LINE_BREAK);\n    return stringBuilder.toString();\n}\n````\n\n---\n### Generar documentación\n\nPara poder generar la documentación UML vamos a hacer uso de la herramienta de código abierto PlantUML que permite a los usuarios crear diagramas UML a partir de un lenguaje de texto sin formato.\n\nLa obtención de este texto se realiza a partir de la información del modelo del dominio recorriendo cada una de las unidades, delegando en estas la generación de de su información en lenguaje PlantUML.\n\nCon el texto generado enviaremos esa información al servidor de PlantUML y nos descargará la imagen generada del diagrama UML. Este punto lo ampliaremos más adelante.\n\n\n\n#### Generar PlantUML\n\nPara poder generar plantuml a partir del modelo del dominio, hacemos uso de la clase ClassDiagramGenerator que recorrerá todas la colección de unidades que previamente le habremos facilitado, delegando en cada una la responsabilidad de generar su información en formato texto para plantuml.\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram classDiagramGenerator.svg)\n\nUn ejemplo de texto PlantUML obtenido de la clase ClassDiagramGenerator\n```\nclass package.Unit1\nclass package.Used\n      package.Unit1 ..\u003e package.Used\n```\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram example print class diagram.svg)\n\nAdemás de obtener la información de todas las unidades añadidas, la clase ClassDiagramGenerate nos provee de una serie de posibilidades a la hora de generar el texto para el plantuml dentro de la información del dominio.\n\n\n##### Obtener PlantUML de un dominio\n\nPartiendo de este código de ejemplo visto anteriormente de la práctica 1 de diseño y calidad del software.\n\n###### Código\n```java \ndomain.addPackage(\"mastermind\")\n    .addUnit(\"Mastermind\")\n        .addBase(\"WithConsoleModel\")\n        .addPart(\"SecretCombination\")\n        .addPart(\"ProposedCombination\")\n        .addPart(\"Result\")\n        .addUsed(\"Message\")               \n    .addUnit(\"Combination\")\n        .addBase(\"WithConsoleModel\")\n        .addPart(\"Color\")\n        .addPart(\"SecretCombination\")\n    .addUnit(\"SecretCombination\")\n        .addBase(\"Combination\")\n        .addUsed(\"ProposedCombination\")\n        .addUsed(\"Message\")\n        .addUsed(\"Result\")\n    .addUnit(\"ProposedCombination\")\n        .addBase(\"Combination\")\n        .addUsed(\"Error\")\n        .addUsed(\"Message\");\n```\n\n\nPara obtener el diagrama de clases visto antes debemos añadir el dominio y posteriormente llamar al método print de la clase ClassDiagramGenerate \n```java\nclassDiagram.addDomain(domain);\nclassDiagram.print();\n```\nPrint() nos devolverá esta cadena de texto\n\n###### Texto\n```\nclass mastermind.Mastermind\nclass mastermind.WithConsoleModel\nclass mastermind.SecretCombination\nclass mastermind.ProposedCombination\nclass mastermind.Result\nclass mastermind.Message\nclass mastermind.Combination\nclass mastermind.Color\nclass mastermind.Error\nmastermind.WithConsoleModel \u003c|-- mastermind.Mastermind\nmastermind.Mastermind *--\u003e mastermind.SecretCombination\nmastermind.Mastermind *--\u003e mastermind.ProposedCombination\nmastermind.Mastermind *--\u003e mastermind.Result\nmastermind.Mastermind ..\u003e mastermind.Message\nmastermind.Combination \u003c|-- mastermind.SecretCombination\nmastermind.SecretCombination ..\u003e mastermind.Message\nmastermind.SecretCombination ..\u003e mastermind.ProposedCombination\nmastermind.SecretCombination ..\u003e mastermind.Result\nmastermind.Combination \u003c|-- mastermind.ProposedCombination\nmastermind.ProposedCombination ..\u003e mastermind.Message\nmastermind.ProposedCombination ..\u003e mastermind.Error\nmastermind.WithConsoleModel \u003c|-- mastermind.Combination\nmastermind.Combination *--\u003e mastermind.Color\nmastermind.Combination *--\u003e mastermind.SecretCombination\n```\n\nQue genera la imagen vista antes\n\n###### Imagen\n![Class Diagram domain model](src/main/resources/images/documentation/diagram exercise 1 design.svg)\n\n##### Acoplamiento Eferente\n\nSiguiendo con el ejemplo anterior, vamos a obtener sólo la unidad SecretCombination y sus relaciones.\n\n###### Código\n```java\nclassDiagram.addUnits(domain.getEfferent(\"SecretCombination\"));\nclassDiagram.print()\n```\n\n###### Texto\n```text\nclass mastermind.SecretCombination\nmastermind.Combination \u003c|-- mastermind.SecretCombination\nmastermind.SecretCombination ..\u003e mastermind.Message\nmastermind.SecretCombination ..\u003e mastermind.ProposedCombination\nmastermind.SecretCombination ..\u003e mastermind.Result\n```\n\n###### Imagen\n![Class Diagram domain model](src/main/resources/images/documentation/diagram exercise 1 design only secret combination.svg)\n\n\n\n##### Acoplamiente Aferente y Eferente\nAdemás de obtener las relaciones de una de las unidades del dominio como acabamos de ver, también es posible obtener el grafo de acoplamiento eferente y aferente tanto de una unidad en concreto como de todo el dominio.\n\nPara estos 4 ejemplos vamos a usar el mismo dominio\n\n```java\ndomain.addUnit(\"X\")\n            .addBase(\"Base_de_X\")\n            .addPart(\"Parte_de_X\")\n            .addAssociate(\"Asociada_de_X\")\n            .addUsed(\"Usada_por_X\")\n       .addUnit(\"Todo_de_X\")\n            .addPart(\"X\")\n       .addUnit(\"Usa_X\")\n            .addUsed(\"X\")\n       .addUnit(\"Asociado_a_X\")\n            .addAssociate(\"X\")\n       .addUnit(\"Descendiente_de_X\")\n            .addBase(\"X\");\n```\n\n\n###### Aferente de todas las unidades del dominio\n######Código\n```java\nclassDiagram.addUnits(domain.getAllAfferent());\nclassDiagram.print();\n```\n\n######Texto\n```\nclass \"afferent X.Todo_de_X\"\nclass \"afferent X.Usa_X\"\nclass \"afferent X.Asociado_a_X\"\nclass \"afferent X.Descendiente_de_X\"\nclass \"afferent Base_de_X.X\"\nclass \"afferent Parte_de_X.X\"\nclass \"afferent Asociada_de_X.X\"\nclass \"afferent Usada_por_X.X\"\n\"afferent X.Todo_de_X\" *--\u003e \"afferent X.X\"\n\"afferent X.Usa_X\" ..\u003e \"afferent X.X\"\n\"afferent X.Asociado_a_X\" --\u003e \"afferent X.X\"\n\"afferent X.X\" \u003c|-- \"afferent X.Descendiente_de_X\"\n\"afferent Base_de_X.Base_de_X\" \u003c|-- \"afferent Base_de_X.X\"\n\"afferent Parte_de_X.X\" *--\u003e \"afferent Parte_de_X.Parte_de_X\"\n\"afferent Asociada_de_X.X\" --\u003e \"afferent Asociada_de_X.Asociada_de_X\"\n\"afferent Usada_por_X.X\" ..\u003e \"afferent Usada_por_X.Usada_por_X\"\n```\n\n######Imagen\n![Class Diagram domain model](src/main/resources/images/documentation/diagram all afferent.svg)\n\n\n###### Aferentes de una clase \n\n###### Código\n```java\nclassDiagram.addUnits(domain.getAfferent(\"X\"));\nclassDiagram.print();\n```\n###### Texto\n```\nclass Todo_de_X\nclass Usa_X\nclass Asociado_a_X\nclass Descendiente_de_X\nTodo_de_X *--\u003e X\nUsa_X ..\u003e X\nAsociado_a_X --\u003e X\nX \u003c|-- Descendiente_de_X\n```\n\n###### Imagen\n![Class Diagram domain model](src/main/resources/images/documentation/diagram afferent for unit x.svg)\n\n\n###### Eferentes de todas las unidades del dominio\n###### Código\n```java\nclassDiagram.addUnits(domain.getAllEfferents());\nclassDiagram.print();\n```\n###### Texto\n```\nclass \"efferent X.X\"\nclass \"efferent Base_de_X.Base_de_X\"\nclass \"efferent Parte_de_X.Parte_de_X\"\nclass \"efferent Asociada_de_X.Asociada_de_X\"\nclass \"efferent Usada_por_X.Usada_por_X\"\nclass \"efferent Todo_de_X.Todo_de_X\"\nclass \"efferent Usa_X.Usa_X\"\nclass \"efferent Asociado_a_X.Asociado_a_X\"\nclass \"efferent Descendiente_de_X.Descendiente_de_X\"\n\"efferent X.Base_de_X\" \u003c|-- \"efferent X.X\"\n\"efferent X.X\" *--\u003e \"efferent X.Parte_de_X\"\n\"efferent X.X\" --\u003e \"efferent X.Asociada_de_X\"\n\"efferent X.X\" ..\u003e \"efferent X.Usada_por_X\"\n\"efferent Todo_de_X.Todo_de_X\" *--\u003e \"efferent Todo_de_X.X\"\n\"efferent Usa_X.Usa_X\" ..\u003e \"efferent Usa_X.X\"\n\"efferent Asociado_a_X.Asociado_a_X\" --\u003e \"efferent Asociado_a_X.X\"\n\"efferent Descendiente_de_X.X\" \u003c|-- \"efferent Descendiente_de_X.Descendiente_de_X\"\n```\n\n###### Imagen\n![Class Diagram domain model](src/main/resources/images/documentation/diagram all efferent.svg)\n\n###### Eferentes de una clase\n###### Código\n```java\nclassDiagram.addUnits(domain.getEfferent(\"X\"));\nclassDiagram.print();\n```\n###### Texto\n```\nclass X\nBase_de_X \u003c|-- X\nX *--\u003e Parte_de_X\nX --\u003e Asociada_de_X\nX ..\u003e Usada_por_X\n```\n\n###### Imagen\n![Class Diagram domain model](src/main/resources/images/documentation/diagram efferent for unit x.svg)\n\n##### Mostrar un paquete concreto\nEn el caso de que tengamos un dominio con varios paquetes, podemos mostrar únicamente un paquete y sus relaciones con los otros paquetes de primer nivel.\n\n###### Código\n```java\ndomain.addPackage(\"mastermind\")\n                .addUnit(\"MastermindStandalone\")\n                \t.addBase(\"Mastermind\")\n                .addPackage(\"mastermind.controllers\")\n                \t.addUnit(\"AcceptorController\")\n                \t\t.addUsed(\"ControllersVisitor\")\n                \t\t.addUnit(\"Logic\")\n                .addPackage(\"masterind\")\n                \t.addUnit(\"Mastermind\")\n                \t.addUsed(\"AcceptorController\")\n                \t.addPart(\"Logic\");\n\nclassDiagram.addDomain(domain);\nclassDiagram.print(\"mastermind\");\n```\n###### Texto\n```\nclass mastermind.MastermindStandalone\nclass mastermind.Mastermind\nclass mastermind.controllers.AcceptorController\nclass mastermind.controllers.Logic\nmastermind.Mastermind \u003c|-- mastermind.MastermindStandalone\nmastermind.Mastermind *--\u003e mastermind.controllers.Logic\nmastermind.Mastermind ..\u003e mastermind.controllers.AcceptorController\n```\n\n###### Imagen de todo el dominio\n![Class Diagram domain model](src/main/resources/images/documentation/diagram all solution v15.6.svg)\n\n###### Imagen del dominio mastermind\n![Class Diagram domain model](src/main/resources/images/documentation/diagram mastermind solution v15.6.svg)\n\n##### Mostrar sólo paquetes\nY por último también podemos ver únicamente los paquetes que componen el dominio.\n\n###### Código\n```java\ndomain.addPackage(\"package1\")\n.addUnit(\"unit1\")\n.addUnit(\"unit2\")\n.addUsed(\"used\")\n.addPackage(\"package2\")\n.addUnit(\"unit3\")\n            .addPackage(\"package3\")\n.addUnit(\"unit4\")\n.addUsed(\"unit1\");\n\nclassDiagram.addDomain(domain);\nclassDiagram.printPackage();\n```\n\n###### Texto\n```\npackage package1 {} \npackage package2 {} \npackage package3 {} \npackage3 ..\u003e package1\n```\n\n###### Imagen\n![Class Diagram domain model](src/main/resources/images/documentation/diagram packages.svg)\n\n\nPodemos ver como únicamente aparecen las relaciones a primer nivel con los demás paquetes.\n\n#### Análisis técnico de los métodos más relevantes\n\n##### ClassDiagramGenerator\n\n###### print\n\nEn este método la clase ClassDiagramGenerator obtiene haciendo uso de cada unidad, la representación del modelo del dominio en lenguaje PlantUML\n````java\npublic String print() {\n    return print(null);\n}\n\npublic String print(String packageDescription) {\n    StringBuilder className = new StringBuilder();\n    StringBuilder relations = new StringBuilder();\n\n    units.forEach(unit -\u003e {\n        if (packageDescription == null ||\n                StringUtils.equals(unit.getMyPackage(), packageDescription)) {\n            className.append(printClass(unit));\n            relations.append(printBase(unit));\n            relations.append(printPart(unit));\n            relations.append(printElement(unit));\n            relations.append(printAssociates(unit));\n            relations.append(printUsed(unit));\n        }\n    });\n    String relationsString = relations.toString();\n    if (packageDescription != null) {\n        units.stream()\n                .filter(unit -\u003e !StringUtils.equals(unit.getMyPackage(), (packageDescription)))\n                .filter(unit -\u003e relationsString.contains(unit.printName()))\n                .forEach(unit -\u003e className.append(printClass(unit)));\n    }\n    return className.toString() + relationsString;\n}\n````\nCon este método podemos obtener todo el modelo del dominio o podemos pedirle que nos devuelva el de un paquete en contreto del modelo. En ese caso, se obtendrá las relaciones de las unidades y toda su información del paquete pedido, y las unidades que se encuentren a primer nivel de realción en otros paquetes.\n\nObtenemos todas las relaciones y su información de las clases pertenecientes al paquete pasado por parámetro.\n````java\nunits.forEach(unit -\u003e {\n        if (packageDescription == null ||\n                StringUtils.equals(unit.getMyPackage(), packageDescription)) {\n            className.append(printClass(unit));\n            relations.append(printBase(unit));\n            relations.append(printPart(unit));\n            relations.append(printElement(unit));\n            relations.append(printAssociates(unit));\n            relations.append(printUsed(unit));\n        }\n    });\n````\nDespués buscamos las relaciones a primer nivel de las unidades del paquete con las unidades de los otros paquetes\n````java\nif (packageDescription != null) {\n        units.stream()\n                .filter(unit -\u003e !StringUtils.equals(unit.getMyPackage(), (packageDescription)))\n                .filter(unit -\u003e relationsString.contains(unit.printName()))\n                .forEach(unit -\u003e className.append(printClass(unit)));\n    }\n````\n\nEl método **printClass(unit)** únicamente hace uso del método **toStringClassFormat** de la clase Unit.\n````java\nprivate String printClass(Unit unit) {\n    return unit.toStringClassFormat();\n}\n````\n\nLos métodos **printBase**, **printPart**, **printElement**, **printAssociates**, **printUsed** recorren los elementos del tipo correspondiente y lo pintan.\n````java\npublic String printPart(Unit unit) {\n    StringBuilder chain = new StringBuilder();\n    unit.getPartList().forEach(p -\u003e chain.append(unit.printName() + PART_RELATIONSHIP + p.printName() + LINE_BREAK));\n    return chain.toString();\n}\n````\n\n###### printPackage\nPara la impresión de los paquetes se hacen uso de dos métodos.\n\n`````java\npublic String printPackage() {\n    StringBuilder sb = new StringBuilder();\n    Set\u003cString\u003e packageDescription = new HashSet\u003c\u003e();\n    units.forEach(e -\u003e {\n        sb.append(printPackage(e, packageDescription));\n        sb.append(printRelationships(e, packageDescription));\n    });\n    return sb.toString();\n}\n`````\nEl primer método, **printPackage** se obtienen los paquetes que componen el modelo del dominio.\n\n````java\nprivate String printPackage(Unit e, Set\u003cString\u003e packageDescription) {\n    StringBuilder sb = new StringBuilder();\n    if (!packageDescription.contains(e.getMyPackage())) {\n        sb.append(\"package \" + e.getMyPackage() + \" {} \" + LINE_BREAK);\n        packageDescription.add(e.getMyPackage());\n    }\n    return sb.toString();\n}\n````\nEn el segundo, **printRelationships**, se obitne la relación entre paquetes.\n````java\nprivate String printRelationships(Unit unit, Set\u003cString\u003e packageDescription) {\n    StringBuilder sb = new StringBuilder();\n    Predicate\u003cUnit\u003e filterNoEqualsPackage = p -\u003e !unit.getMyPackage().equals(p.getMyPackage());\n    Predicate\u003cUnit\u003e filterNoContainsPackage = p -\u003e !packageDescription.contains(unit.getMyPackage() + USE_RELATIONSHIP + p.getMyPackage());\n    Consumer\u003cUnit\u003e addRelationship = p -\u003e {\n        sb.append(unit.getMyPackage() + USE_RELATIONSHIP + p.getMyPackage() + LINE_BREAK);\n        packageDescription.add(unit.getMyPackage() + USE_RELATIONSHIP + p.getMyPackage());\n    };\n\n    unit.getPartList().stream().filter(filterNoEqualsPackage).filter(filterNoContainsPackage).forEach(addRelationship);\n    unit.getUsed().stream().filter(filterNoEqualsPackage).filter(filterNoContainsPackage).forEach(addRelationship);\n    unit.getAssociates().stream().filter(filterNoEqualsPackage).filter(filterNoContainsPackage).forEach(addRelationship);\n    unit.getElements().stream().filter(filterNoEqualsPackage).filter(filterNoContainsPackage).forEach(addRelationship);\n    unit.getBase().stream().filter(filterNoEqualsPackage).filter(filterNoContainsPackage).forEach(addRelationship);\n\n    return sb.toString();\n}\n````\nEste método se compone de dos **Predicate** y un **Consumer**. Los predicados filtran todas aquellas unidades cuyo paquete no sea igual al que pasamos por parámetro y que esa relación no se haya añadido ya.\nEl consumidor añade esa relación al StringBuilder y a la variable packageDescription que usamos en el predicado anterior.\n\n---\n\n#### Generar gráfico\n\nEn este punto se hizo una investigación para poder invocar al servidor PlantUML desde la propia herramienta, facilitando así la creación de la documentación UML en formato gráfico y no sólo en lenguaje PlantUML.\n\nPara ello se hizo un trabajo de ingeniería inversa de la web planttext.com, para saber como codificar de forma adecuada el lenguaje PlantUML de manera que el servidor lo entienda correctamente y de esta forma nos genere la imagen.\n\nEn el análisis se descubrió que son necesarias dos ficheros JavaScript, en las cuales se encuentran una serie de métodos que son los encargados de codificar la información, por lo que se ha optado por descargar estos ficheros cada vez que vayamos a generar una imagen por si hubiera actualizaciones, para poder usarlos localmente. Estos ficheros se encuentra en la ruta resources/js\n\nLa dirección del servidor, así como los nombres de los ficheros JavaScript, junto con otras propiedades, están definidas en una archivo de configuración (application.properties).\n\nEste sería el diagrama de clases de la clase GenerateImage encarga de contactar con el servidor PlantUML para descargar la imagen en nuestra máquina.\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram generateImage.svg)\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram node plantuml.svg)\n\nEn este diagrama se muestra la conexión que establece nuestra máquina con el servidor de PlantUML.\n\n#### Análisis técnico de los métodos más relevantes\n\n##### GenerateImage\n\n###### loadFromProperties\nEste método se encarga de obtener la información configurada en el fichero de propiedades application.properties\n````java\nprivate static void loadFromProperties(){\n    try(InputStream configStream =GenerateImage.class.getResourceAsStream( \"/application.properties\")){\n        props.load(configStream);\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n````\n\n###### inicialized\nEste método es el encargado de setear la información obtenido del fichero de propiedades en los atributos de la clase\n````java\nprivate static void inicialized(){\n    loadFromProperties();\n    path=props.getProperty(\"img.folder\");\n    jsPath=props.getProperty(\"js.folder\");\n    name=props.getProperty(\"img.name.default\");\n    rawdeflate=props.getProperty(\"rawdeflate.file\");\n    plantumlJs=props.getProperty(\"plantuml.file\");\n    urlJs=props.getProperty(\"plantuml.url.js\");\n    urlApi=props.getProperty(\"plantuml.url.api\");\n    url=props.getProperty(\"plantuml.url\");\n    format=props.getProperty(\"plantuml.url.format.image\");\n}\n````\n\n####### downloadFilesToEncode\nEste métedo se encarga de descargar de los servidores PlantUML los dos ficheros que son necesarios para poder codificar el lenguaje PlantUML y que el servidor entienda la información para que nos devuelva la imagen generada.\n\n`````java\nprivate static void downloadFilesToEncode() throws IOException {\n    try (InputStream in = new URL(createPath(url,urlJs,rawdeflate)).openStream()) {\n        Files.deleteIfExists(Path.of(createPath(jsPath,rawdeflate)).toAbsolutePath());\n        Files.copy(in, Paths.get(createPath(jsPath,rawdeflate)).toAbsolutePath());\n    } catch (MalformedURLException | FileNotFoundException e) {\n        LOG.debug(\"context\",e);\n    }\n\n    try (InputStream in = new URL(createPath(url, urlJs,plantumlJs)).openStream()) {\n        Files.deleteIfExists(Path.of(createPath(jsPath,plantumlJs)).toAbsolutePath());\n        Files.copy(in, Paths.get(createPath(jsPath,plantumlJs)).toAbsolutePath());\n    } catch (MalformedURLException | FileNotFoundException e) {\n        LOG.debug(\"context\", e);\n    }\n}\n`````\n###### deflate\nMetódo que hace uso de uno de los ficheros de js descargados que hemos visto en el método anterior y ejecuta el método deflate.\n````java\nprivate static String deflate(String classDiagram) throws IOException, ScriptException, NoSuchMethodException {\n    ScriptEngine engine = manager.getEngineByName(\"JavaScript\");\n    engine.eval(Files.newBufferedReader(Paths.get(createPath(jsPath,rawdeflate)).toAbsolutePath(), StandardCharsets.UTF_8));\n\n    Invocable inv = (Invocable) engine;\n    return (String) inv.invokeFunction(\"deflate\", classDiagram);\n}\n````\n###### encode64\nAl igual que el anterior método, este hace uso del otro fichero llamando al método encode64.\n````java\nprivate static String encode64(String classDiagram) throws IOException, ScriptException, NoSuchMethodException {\n    ScriptEngine engine = manager.getEngineByName(\"JavaScript\");\n    engine.eval(Files.newBufferedReader(Paths.get(createPath(jsPath,plantumlJs)).toAbsolutePath(), StandardCharsets.UTF_8));\n\n    Invocable inv = (Invocable) engine;\n    return (String) inv.invokeFunction(\"encode64\", classDiagram);\n}\n````\n###### downloadImage\nEste método se encarga de llamar a los servidores de PlantUML.\n````java\npublic static String downloadImage(String classDiagramEncode, String nameUser, String pathUser) throws NoSuchMethodException, ScriptException, IOException {\n    inicialized();\n    downloadFilesToEncode();\n    checkAndCreatePath(pathUser);\n    checkName(nameUser);\n    String encodingClassDiagram = encode64(deflate(classDiagramEncode));\n    try (InputStream in = new URL(createPath(url,urlApi,format,\"/\") + encodingClassDiagram).openStream()) {\n        Files.copy(in, Paths.get(path + name));\n    } catch (IOException e) {\n        LOG.debug(\"context\",e);\n    }\n    return name;\n}\n````\n\n\n\n---\n### Ingeniería inversa\n#### Descargar código\nTambién disponemos de la opción de descargarnos el código de un repositorio git para su posterior análisis. La clase encargada de esto es CloneRepository, que clonará el repositorio en la ruta resources/repositories/{nombreProyecto}\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram cloneRepository.svg)\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram node git.svg)\n\nDiagrama que muestra la conexión de nuestra máquina con el servidor que aloja el repositorio de de Git.\n\n\n#### Analizar código\n\nComo se ha comentado anteriormente, también es posible realizar un análisis mediante ingeniería inversa del código de una carpeta, usando el patrón Interpreter y el patrón Visitor, para crear un grafo del modelo del dominio. Este análisis también es posible realizarlo sobre una única clase. \n\nEl modelo del dominio generado será únicamente de las relaciones entre las diferentes unidades sin entrar en los atributos o funciones de estas.\n\nPara poder realizar el análisis es necesario invocar el método run de la clase JavaAnalyzerEclipseAST pasándole por parámetro la ruta donde se encuentra el código a analizar. \n\nEste es el diagrama de clases de la clase JavaAnalyzerEclipseAST\n\n![Class Diagram domain model](src/main/resources/images/documentation/diagram JavaAnalyzerEclipseAST.svg)\n\nA la clase ASTParser se le suministra la ruta con el fichero que va a interpretar y el procesado de ese fichero, se lo pasamos por parámetro a la clase Compilation Unit.\n\nEsta clase CompilationUnit usa la clase ASTVisitor, y mediante el patrón visitor ua las diferentes clases que se observan en el diagrama de clases para poder obtener la información e ir modelando el modelo del domino.\n\n\nEl resultado de la ejecución nos devolverá el modelo del dominio creado.\n\n#### Análisis técnico de los métodos más relevantes\n\n##### JavaAnalyzerEclipseAST\n\n###### run\nEste método recorre todos los elementos que posee una carpeta y sus subcarpetas de forma recursiva para su análisis con el méetodo **analyzed**\n\n````java\npublic static Domain run(String path) {\n    Domain domain = new Domain(\"domain\");\n    Optional\u003cPath\u003e hit = Optional.empty();\n\n    try (Stream\u003cPath\u003e entries = Files.walk(Path.of(System.getProperty(\"user.dir\")))) {\n        hit = entries.filter(file -\u003e file.toString().contains(path))\n                .findAny();\n\n    } catch (IOException e) {\n        LOG.debug(\"context\", e);\n    }\n\n    try (Stream\u003cPath\u003e entries\n                 = Files.walk(Paths.get(hit.stream().findFirst().orElse(null).toString()).toAbsolutePath())) {\n        entries.forEach(ruta -\u003e {\n            if (Files.isRegularFile(ruta)) {\n                try {\n                    analyzed(domain, ruta.toString());\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        });\n    } catch (IOException e) {\n        LOG.debug(\"context\", e);\n    }\n    return domain;\n}\n````\n\n###### analyzed\nEn este método se inicializan las variables **primitives**, **objects** y **objectJavaAnalyzer** que serán ignorados a la hora de realizar el análisis.\n````java\n...\nSet\u003cString\u003e primitives = new HashSet\u003c\u003e(Arrays.asList(\"int\", \"byte\", \"short\", \"long\", \"float\", \"double\", \"boolean\", \"char\"));\nSet\u003cString\u003e objects = new HashSet\u003c\u003e(Arrays.asList(\"RuntimeException\", \"AssertionError\", \"Logger\", \"Map\", \"Set\", \"BufferedReader\", \"Random\", \"Collections\", \"Arrays\", \"Files\", \"LOG\", \"Objects\", \"System\", \"SpringApplication\", \"CommandLineRunner\", \"JavaClasses\", \"SecureRandom\", \"JavaClass\", \"ObjectInputStream\", \"ByteArrayInputStream\", \"ObjectOutputStream\", \"ByteArrayOutputStream\", \"Object\", \"Serializable\", \"String\", \"StringBuilder\", \"ProcessBuilder\", \"Path\", \"Process\", \"InputStream\", \"Thread\", \"Invocable\", \"ScriptEngine\"));\nSet\u003cString\u003e objectJavaAnalyzer = new HashSet\u003c\u003e(Arrays.asList(\"CompilationUnit\", \"TypeDeclaration\",\n\"SimpleName\",\n\"MethodDeclaration\",\n\"ClassInstanceCreation\",\n\"EnumDeclaration\",\n\"ParameterizedType\",\n\"Assignment\",\n\"VariableDeclarationFragment\",\n\"ExpressionStatement\",\n\"VariableDeclarationStatement\",\n\"PackageDeclaration\"));\nobjects.addAll(objectJavaAnalyzer);\n...\n````\nPosteriormente se realiza el parseo de la clase para su análisis\n````java\n  ASTParser parser = ASTParser.newParser(AST.JLS8);\n        parser.setSource(readFileToString(pathFile));\n        parser.setKind(ASTParser.K_COMPILATION_UNIT);\n        parser.setBindingsRecovery(true);\n        parser.setResolveBindings(true);\n        parser.setStatementsRecovery(true);\n        Map options = JavaCore.getOptions();\n        options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_5); //or newer version\n        parser.setCompilerOptions(options);\n````\nUna vez hecho el parseo se crea un **CompilationUnit** y mediante el patrón visitor vamos analizando los diferentes elementos de la case\n````java\nfinal CompilationUnit cu = (CompilationUnit) parser.createAST(null);\n    cu.accept(new ASTVisitor() {\n\n        public boolean visit(VariableDeclarationFragment node) {\n\n            if (node.getParent() instanceof FieldDeclaration) {\n                fields.add(((FieldDeclaration) node.getParent()).getType().toString());\n            }\n            return false; // do not continue to avoid usage info\n        }\n            \n...\n````\nEl método **accept** posee varios métodos visit que podremos usar para procesar la información dependiendo de si queremos atributos, imports, paquete, etc.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmastercloudapps-projects%2Fumldiagramslib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmastercloudapps-projects%2Fumldiagramslib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmastercloudapps-projects%2Fumldiagramslib/lists"}