From 7e81bfef28cff8589627de4a9d4648c89094dfa8 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Mon, 17 Jun 2024 00:28:50 +0200 Subject: [PATCH] [FEAT] add generation of dot files --- .../externalRestApi/DotGenerateApi.java | 230 +++++++++ .../externalRestApi/dot/DotApiGeneration.java | 445 ++++++++++++++++ .../externalRestApi/dot/DotClassElement.java | 474 ++++++++++++++++++ .../dot/DotClassElementGroup.java | 27 + 4 files changed, 1176 insertions(+) create mode 100644 src/org/kar/archidata/externalRestApi/DotGenerateApi.java create mode 100644 src/org/kar/archidata/externalRestApi/dot/DotApiGeneration.java create mode 100644 src/org/kar/archidata/externalRestApi/dot/DotClassElement.java create mode 100644 src/org/kar/archidata/externalRestApi/dot/DotClassElementGroup.java diff --git a/src/org/kar/archidata/externalRestApi/DotGenerateApi.java b/src/org/kar/archidata/externalRestApi/DotGenerateApi.java new file mode 100644 index 0000000..2ac45d0 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/DotGenerateApi.java @@ -0,0 +1,230 @@ +package org.kar.archidata.externalRestApi; + +import java.io.FileWriter; +import java.io.InputStream; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.glassfish.jersey.media.multipart.ContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.kar.archidata.catcher.RestErrorResponse; +import org.kar.archidata.externalRestApi.dot.DotApiGeneration; +import org.kar.archidata.externalRestApi.dot.DotClassElement; +import org.kar.archidata.externalRestApi.dot.DotClassElement.DefinedPosition; +import org.kar.archidata.externalRestApi.dot.DotClassElementGroup; +import org.kar.archidata.externalRestApi.model.ApiGroupModel; +import org.kar.archidata.externalRestApi.model.ClassModel; + +public class DotGenerateApi { + + public static void generateApi(final AnalyzeApi api, final String pathDotFile) throws Exception { + final List localModel = generateApiModel(api); + final DotClassElementGroup dotGroup = new DotClassElementGroup(localModel); + + try (final FileWriter myWriter = new FileWriter(pathDotFile)) { + myWriter.write(""" + # Architecture auto-generated file + digraph UML_Class_diagram { + #rankdir=NS; + graph [ + pad="0.5" + nodesep="1" + #ranksep="2" + label="Rest API server Model" + labelloc="t" + fontname="FreeMono,Sans-Mono,Helvetica,Arial,sans-serif" + ] + node [ + fontname="FreeMono,Sans-Mono,Helvetica,Arial,sans-serif" + shape=record + style=filled + fillcolor=gray95 + ] + edge [fontname="FreeMono,Sans-Mono,Helvetica,Arial,sans-serif"] + """); + /* + myWriter.write(""" + subgraph REST_API { + style=filled; + color=lightgrey; + label="REST API"; + rankdir=LR; + """); + */ + for (final ApiGroupModel element : api.apiModels) { + final String tmp = DotApiGeneration.generateApiFile(element, dotGroup); + myWriter.write(tmp); + myWriter.write("\n"); + } + // create an invisible link to force all element to be link together: + String previous = null; + for (final ApiGroupModel element : api.apiModels) { + if (previous == null) { + previous = element.name; + continue; + } + myWriter.write("\t{ "); + myWriter.write(previous); + myWriter.write(":s -> "); + previous = element.name; + myWriter.write(previous); + myWriter.write(":n [style=invis]}\n"); + } + /* + myWriter.write(""" + } + """); + myWriter.write(""" + subgraph Models { + style=filled; + color=lightgrey; + label="Models"; + rankdir=NS; + """); + */ + // Generates all MODEL files + for (final DotClassElement element : localModel) { + final String tmp = element.generateFile(dotGroup); + myWriter.write(tmp); + myWriter.write("\n"); + } + /* + myWriter.write(""" + } + """); + */ + myWriter.write(""" + } + """); + } + } + + private static List generateApiModel(final AnalyzeApi api) throws Exception { + // First step is to add all specific basic elements the wrap correctly the model + final List dotModels = new ArrayList<>(); + List models = api.getCompatibleModels(List.of(Void.class, void.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "void", "void", null, null, DefinedPosition.NATIVE)); + } + models = api.getCompatibleModels(List.of(Object.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "Object", "object", null, "Object", DefinedPosition.NATIVE)); + } + // Map is binded to any ==> can not determine this complex model for now + models = api.getCompatibleModels(List.of(Map.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "Object", "any", null, null, DefinedPosition.NATIVE)); + } + models = api.getCompatibleModels(List.of(String.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "String", "string", null, "String", DefinedPosition.NATIVE)); + } + models = api.getCompatibleModels( + List.of(InputStream.class, FormDataContentDisposition.class, ContentDisposition.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "File", "File", null, "File", DefinedPosition.NATIVE)); + } + models = api.getCompatibleModels(List.of(Boolean.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "Boolean", "boolean", null, "Boolean", DefinedPosition.NATIVE)); + } + models = api.getCompatibleModels(List.of(boolean.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "boolean", "boolean", null, "boolean", DefinedPosition.NATIVE)); + } + models = api.getCompatibleModels(List.of(UUID.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "UUID", "UUID", "isUUID", "UUID", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(long.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "long", "Long", "isLong", "long", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(Long.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "Long", "Long", "isLong", "Long", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(short.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "short", "Short", "isShort", "short", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(Short.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "Short", "Short", "isShort", "Short", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(int.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "int", "Integer", "isInteger", "int", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(Integer.class)); + if (models != null) { + dotModels.add( + new DotClassElement(models, "Integer", "Integer", "isInteger", "Integer", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(double.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "Double", "Double", "isDouble", "double", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(Double.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "Double", "Double", "isDouble", "Double", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(float.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "float", "Float", "isFloat", "float", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(Float.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "Float", "Float", "isFloat", "Float", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(Instant.class)); + if (models != null) { + dotModels.add( + new DotClassElement(models, "Instant", "Instant", "isInstant", "Instant", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(Date.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "Date", "IsoDate", "isIsoDate", "Date", DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(Timestamp.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "Timestamp", "Timestamp", "isTimestamp", "Timestamp", + DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(LocalDate.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "LocalDate", "LocalDate", "isLocalDate", "LocalDate", + DefinedPosition.BASIC)); + } + models = api.getCompatibleModels(List.of(LocalTime.class)); + if (models != null) { + dotModels.add(new DotClassElement(models, "LocalTime", "LocalTime", "isLocalTime", "LocalTime", + DefinedPosition.BASIC)); + } + // needed for Rest interface + api.addModel(RestErrorResponse.class); + for (final ClassModel model : api.getAllModel()) { + boolean alreadyExist = false; + for (final DotClassElement elem : dotModels) { + if (elem.isCompatible(model)) { + alreadyExist = true; + break; + } + } + if (alreadyExist) { + continue; + } + dotModels.add(new DotClassElement(model)); + } + return dotModels; + + } + +} diff --git a/src/org/kar/archidata/externalRestApi/dot/DotApiGeneration.java b/src/org/kar/archidata/externalRestApi/dot/DotApiGeneration.java new file mode 100644 index 0000000..23e372c --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/dot/DotApiGeneration.java @@ -0,0 +1,445 @@ +package org.kar.archidata.externalRestApi.dot; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +import org.kar.archidata.externalRestApi.dot.DotClassElement.DefinedPosition; +import org.kar.archidata.externalRestApi.model.ApiGroupModel; +import org.kar.archidata.externalRestApi.model.ApiModel; +import org.kar.archidata.externalRestApi.model.ClassEnumModel; +import org.kar.archidata.externalRestApi.model.ClassListModel; +import org.kar.archidata.externalRestApi.model.ClassMapModel; +import org.kar.archidata.externalRestApi.model.ClassModel; +import org.kar.archidata.externalRestApi.model.ClassObjectModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DotApiGeneration { + static final Logger LOGGER = LoggerFactory.getLogger(DotApiGeneration.class); + + public static String generateClassEnumModelTypescript( + final ClassEnumModel model, + final DotClassElementGroup dotGroup, + final Set imports) throws IOException { + imports.add(model); + final DotClassElement dotModel = dotGroup.find(model); + return dotModel.dotTypeName; + } + + public static String generateClassObjectModelTypescript( + final ClassObjectModel model, + final DotClassElementGroup dotGroup, + final Set imports) throws IOException { + final DotClassElement dotModel = dotGroup.find(model); + if (dotModel.nativeType != DefinedPosition.NATIVE) { + imports.add(model); + } + if (dotModel.nativeType != DefinedPosition.NORMAL) { + return dotModel.dotTypeName; + } + return dotModel.dotTypeName; + } + + public static String generateClassMapModelTypescript( + final ClassMapModel model, + final DotClassElementGroup dotGroup, + final Set imports) throws IOException { + final StringBuilder out = new StringBuilder(); + out.append("Map<"); + out.append(generateClassModelTypescript(model.keyModel, dotGroup, imports)); + out.append(", "); + out.append(generateClassModelTypescript(model.valueModel, dotGroup, imports)); + out.append(">"); + return out.toString(); + } + + public static String generateClassListModelTypescript( + final ClassListModel model, + final DotClassElementGroup dotGroup, + final Set imports) throws IOException { + final StringBuilder out = new StringBuilder(); + out.append("List<"); + out.append(generateClassModelTypescript(model.valueModel, dotGroup, imports)); + out.append(">"); + return out.toString(); + } + + public static String generateClassModelTypescript( + final ClassModel model, + final DotClassElementGroup dotGroup, + final Set imports) throws IOException { + if (model instanceof final ClassObjectModel objectModel) { + return generateClassObjectModelTypescript(objectModel, dotGroup, imports); + } + if (model instanceof final ClassListModel listModel) { + return generateClassListModelTypescript(listModel, dotGroup, imports); + } + if (model instanceof final ClassMapModel mapModel) { + return generateClassMapModelTypescript(mapModel, dotGroup, imports); + } + if (model instanceof final ClassEnumModel enumModel) { + return generateClassEnumModelTypescript(enumModel, dotGroup, imports); + } + throw new IOException("Impossible model:" + model); + } + + public static String generateClassModelsTypescript( + final List models, + final DotClassElementGroup dotGroup, + final Set imports) throws IOException { + if (models.size() == 0) { + return "void"; + } + final StringBuilder out = new StringBuilder(); + boolean isFirst = true; + for (final ClassModel model : models) { + if (isFirst) { + isFirst = false; + } else { + out.append(" | "); + } + final String data = generateClassModelTypescript(model, dotGroup, imports); + out.append(data); + } + return out.toString(); + } + + public static List generateClassModelsLinks( + final List models, + final DotClassElementGroup dotGroup) throws IOException { + // a ce point ca fait les union et tout et tou, mais il vas faloir fusionner avec les class ... + ICI CA PLANTE !!! + if (models.size() == 0) { + return null; + } + final StringBuilder out = new StringBuilder(); + boolean isFirst = true; + for (final ClassModel model : models) { + if (isFirst) { + isFirst = false; + } else { + out.append(" | "); + } + final String data = generateClassModelTypescript(model, dotGroup, imports); + out.append(data); + } + return out.toString(); + } + + public static String capitalizeFirstLetter(final String str) { + if (str == null || str.isEmpty()) { + return str; + } + return str.substring(0, 1).toUpperCase() + str.substring(1); + } + + public static String generateApiFile(final ApiGroupModel element, final DotClassElementGroup dotGroup) + throws IOException { + final StringBuilder data = new StringBuilder(); + final String polkop = """ + API_REST_PLOP [ + shape=plain + label=< + + + + + + +
MY_CLASS_NAME
(REST)
+ + + + + + + +
+ + plop(xxx: Kaboom) : KataPloof
+     /qsdqds/{id}/ +
+ + plop(xxx: Kaboom) : KataPloof
+     /qsdqds/{id}/ +
+
> + ] + """; + data.append(""" + %s [ + shape=plain + label=< + + + + + + +
%s
(REST)
+ + """.formatted(element.name, element.name)); + final Set imports = new HashSet<>(); + final Set zodImports = new HashSet<>(); + final Set isImports = new HashSet<>(); + final Set writeImports = new HashSet<>(); + final Set toolImports = new HashSet<>(); + for (final ApiModel interfaceElement : element.interfaces) { + final List consumes = interfaceElement.consumes; + final List produces = interfaceElement.produces; + final boolean needGenerateProgress = interfaceElement.needGenerateProgress; + /* + if (returnComplexModel != null) { + data.append(returnComplexModel.replaceAll("(?m)^", "\t")); + for (final ClassModel elem : interfaceElement.returnTypes) { + zodImports.addAll(elem.getDependencyGroupModels()); + } + } + */ + data.append("\t\t\t\t\t\n\t\t\t\t\t\t\t\n"); + } + /* + data.append("\n}\n"); + + final StringBuilder out = new StringBuilder(); + + final List toolImportsList = new ArrayList<>(toolImports); + Collections.sort(toolImportsList); + if (toolImportsList.size() != 0) { + out.append("import {"); + for (final String elem : toolImportsList) { + out.append("\n\t"); + out.append(elem); + out.append(","); + } + out.append("\n} from \"../rest-tools\";\n\n"); + } + + if (zodImports.size() != 0) { + out.append("import { z as zod } from \"zod\"\n"); + } + + final Set finalImportSet = new TreeSet<>(); + + for (final ClassModel model : imports) { + final DotClassElement dotModel = dotGroup.find(model); + if (dotModel.nativeType == DefinedPosition.NATIVE) { + continue; + } + finalImportSet.add(dotModel.dotTypeName); + } + for (final ClassModel model : isImports) { + final DotClassElement dotModel = dotGroup.find(model); + if (dotModel.nativeType == DefinedPosition.NATIVE) { + continue; + } + if (dotModel.dotCheckType != null) { + finalImportSet.add(dotModel.dotCheckType); + } + } + for (final ClassModel model : zodImports) { + final DotClassElement dotModel = dotGroup.find(model); + if (dotModel.nativeType == DefinedPosition.NATIVE) { + continue; + } + finalImportSet.add("Zod" + dotModel.dotTypeName); + } + for (final ClassModel model : writeImports) { + final DotClassElement dotModel = dotGroup.find(model); + if (dotModel.nativeType == DefinedPosition.NATIVE) { + continue; + } + finalImportSet.add(dotModel.dotTypeName + "Write"); + } + + if (finalImportSet.size() != 0) { + out.append("import {"); + for (final String elem : finalImportSet) { + out.append("\n\t"); + out.append(elem); + out.append(","); + } + out.append("\n} from \"../model\";\n\n"); + } + + out.append(data.toString()); + */ + + data.append(""" +
+ "); + data.append(interfaceElement.name); + data.append("("); + boolean hasParam = false; + /* + if (!interfaceElement.queries.isEmpty()) { + data.append("\n\t\tqueries: {"); + for (final Entry> queryEntry : interfaceElement.queries.entrySet()) { + data.append("\n\t\t\t"); + data.append(queryEntry.getKey()); + data.append("?: "); + data.append(generateClassModelsTypescript(queryEntry.getValue(), dotGroup, imports, false)); + data.append(","); + } + data.append("\n\t\t},"); + } + */ + /* fonctionnel mais trop de donnée + if (!interfaceElement.parameters.isEmpty()) { + //data.append("params: {"); + for (final Entry> paramEntry : interfaceElement.parameters.entrySet()) { + data.append(""); + data.append(paramEntry.getKey()); + data.append(": "); + data.append(generateClassModelsTypescript(paramEntry.getValue(), dotGroup, imports, false)); + data.append(","); + } + //data.append("},"); + } + */ + if (interfaceElement.unnamedElement.size() == 1) { + if (hasParam) { + data.append(", "); + } + hasParam = true; + data.append("data: "); + data.append( + generateClassModelTypescript(interfaceElement.unnamedElement.get(0), dotGroup, writeImports)); + } else if (interfaceElement.multiPartParameters.size() != 0) { + if (hasParam) { + data.append(", "); + } + hasParam = true; + boolean hasParam2 = false; + data.append("data: {"); + for (final Entry> pathEntry : interfaceElement.multiPartParameters + .entrySet()) { + if (hasParam2) { + data.append(", "); + } + hasParam2 = true; + data.append(pathEntry.getKey()); + data.append(": "); + data.append(generateClassModelsTypescript(pathEntry.getValue(), dotGroup, writeImports)); + } + data.append("}"); + } + data.append("): "); + /* + String tmp = DotClassElement.generateLocalModel( + final String ModelName, + final List models, + final DotClassElementGroup dotGroup) + public static String generateClassModelsTypescript( + final List models, + final DotClassElementGroup dotGroup, + final Set imports) throws IOException { + */ + /*if (returnComplexModel != null) { + data.append(returnModelNameIfComplex); + } else*/ { + if (interfaceElement.returnTypes instanceof ClassEnumModel) { + final DotClassElement dotFieldModel = dotGroup.find(interfaceElement.returnTypes); + data.append(dotFieldModel.dotTypeName); + outLinks.append("\t"); + outLinks.append(this.dotTypeName); + outLinks.append(":"); + outLinks.append(field.name()); + outLinks.append(":e -> "); + outLinks.append(dotFieldModel.dotTypeName); + outLinks.append(":NAME:w\n"); + } else { + final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, dotGroup, + imports); + data.append(returnType); + } + } + + data.append(""); + //data.append("
    "); + data.append("
"); + /* + data.append("\n\t\t\trestModel: {"); + data.append("\n\t\t\t\tendPoint: \""); + */ + data.append(interfaceElement.restEndPoint); + /* + data.append("\","); + data.append("\n\t\t\t\trequestType: HTTPRequestModel."); + toolImports.add("HTTPRequestModel"); + data.append(interfaceElement.restTypeRequest); + data.append(","); + if (consumes != null) { + for (final String elem : consumes) { + if (MediaType.APPLICATION_JSON.equals(elem)) { + data.append("\n\t\t\t\tcontentType: HTTPMimeType.JSON,"); + toolImports.add("HTTPMimeType"); + break; + } else if (MediaType.MULTIPART_FORM_DATA.equals(elem)) { + data.append("\n\t\t\t\tcontentType: HTTPMimeType.MULTIPART,"); + toolImports.add("HTTPMimeType"); + break; + } else if (MediaType.TEXT_PLAIN.equals(elem)) { + data.append("\n\t\t\t\tcontentType: HTTPMimeType.TEXT_PLAIN,"); + toolImports.add("HTTPMimeType"); + break; + } + } + } else if (RestTypeRequest.DELETE.equals(interfaceElement.restTypeRequest)) { + data.append("\n\t\t\t\tcontentType: HTTPMimeType.TEXT_PLAIN,"); + toolImports.add("HTTPMimeType"); + } + if (produces != null) { + if (produces.size() > 1) { + data.append("\n\t\t\t\taccept: produce,"); + } else { + final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, dotGroup, + imports, false); + if (!"void".equals(returnType)) { + for (final String elem : produces) { + if (MediaType.APPLICATION_JSON.equals(elem)) { + data.append("\n\t\t\t\taccept: HTTPMimeType.JSON,"); + toolImports.add("HTTPMimeType"); + break; + } + } + } + } + } + data.append("\n\t\t\t},"); + data.append("\n\t\t\trestConfig,"); + if (!interfaceElement.parameters.isEmpty()) { + data.append("\n\t\t\tparams,"); + } + if (!interfaceElement.queries.isEmpty()) { + data.append("\n\t\t\tqueries,"); + } + if (interfaceElement.unnamedElement.size() == 1) { + data.append("\n\t\t\tdata,"); + } else if (interfaceElement.multiPartParameters.size() != 0) { + data.append("\n\t\t\tdata,"); + } + if (needGenerateProgress) { + data.append("\n\t\t\tcallback,"); + } + data.append("\n\t\t}"); + if (returnComplexModel != null) { + data.append(", is"); + data.append(returnModelNameIfComplex); + } else { + final DotClassElement retType = dotGroup.find(interfaceElement.returnTypes.get(0)); + if (retType.dotCheckType != null) { + data.append(", "); + data.append(retType.dotCheckType); + imports.add(interfaceElement.returnTypes.get(0)); + } + } + */ + data.append("
+
> + ] + """); + return data.toString(); + } + +} \ No newline at end of file diff --git a/src/org/kar/archidata/externalRestApi/dot/DotClassElement.java b/src/org/kar/archidata/externalRestApi/dot/DotClassElement.java new file mode 100644 index 0000000..24e1cea --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/dot/DotClassElement.java @@ -0,0 +1,474 @@ +package org.kar.archidata.externalRestApi.dot; + +import java.io.IOException; +import java.util.List; +import java.util.Map.Entry; + +import org.kar.archidata.externalRestApi.model.ClassEnumModel; +import org.kar.archidata.externalRestApi.model.ClassListModel; +import org.kar.archidata.externalRestApi.model.ClassMapModel; +import org.kar.archidata.externalRestApi.model.ClassModel; +import org.kar.archidata.externalRestApi.model.ClassObjectModel; +import org.kar.archidata.externalRestApi.model.ClassObjectModel.FieldProperty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DotClassElement { + static final Logger LOGGER = LoggerFactory.getLogger(DotClassElement.class); + + public enum DefinedPosition { + NATIVE, // Native element of dot language. + BASIC, // basic wrapping for JAVA type. + NORMAL // Normal Object to interpret. + } + + public List models; + public String zodName; + public String dotTypeName; + public String dotCheckType; + public String declaration; + public String fileName = null; + public String comment = null; + public DefinedPosition nativeType = DefinedPosition.NORMAL; + + public static String determineFileName(final String className) { + return className.replaceAll("([a-z])([A-Z])", "$1-$2").replaceAll("([A-Z])([A-Z][a-z])", "$1-$2").toLowerCase(); + } + + public DotClassElement(final List model, final String zodName, final String dotTypeName, + final String dotCheckType, final String declaration, final DefinedPosition nativeType) { + this.models = model; + this.zodName = zodName; + this.dotTypeName = dotTypeName; + this.declaration = declaration; + this.nativeType = nativeType; + } + + public DotClassElement(final ClassModel model) { + this.models = List.of(model); + this.dotTypeName = model.getOriginClasses().getSimpleName(); + this.declaration = null; + } + + public boolean isCompatible(final ClassModel model) { + return this.models.contains(model); + } + + public String generateEnum(final ClassEnumModel model, final DotClassElementGroup dotGroup) throws IOException { + final StringBuilder out = new StringBuilder(); + out.append(""" + %s [ + shape=plain + label=< + + + + + + +
%s
(ENUM)
+ + """.formatted(this.dotTypeName, this.dotTypeName)); + final boolean first = true; + for (final Entry elem : model.getListOfValues().entrySet()) { + out.append("\t\t\t\t\t\t\n"); + } + out.append(""" +
+ "); + out.append(elem.getKey()); + out.append(" = "); + if (elem.getValue() instanceof final Integer value) { + out.append(value); + } else { + out.append("'"); + out.append(elem.getValue()); + out.append("'"); + } + out.append("
+
> + ] + """); + return out.toString(); + } + + public String generateImporDot(final List depModels, final DotClassElementGroup dotGroup) + throws IOException { + final StringBuilder out = new StringBuilder(); + for (final ClassModel depModel : depModels) { + final DotClassElement dotModel = dotGroup.find(depModel); + if (dotModel.nativeType != DefinedPosition.NATIVE) { + out.append("import {"); + out.append(dotModel.zodName); + out.append("} from \"./"); + out.append(dotModel.fileName); + out.append("\";\n"); + } + } + return out.toString(); + } + + private Object generateComment(final ClassObjectModel model) { + final StringBuilder out = new StringBuilder(); + if (model.getDescription() != null || model.getExample() != null) { + out.append("/**\n"); + if (model.getDescription() != null) { + for (final String elem : model.getDescription().split("\n")) { + out.append(" * "); + out.append(elem); + out.append("\n"); + } + } + if (model.getExample() != null) { + out.append(" * Example:\n"); + out.append(" * ```\n"); + for (final String elem : model.getExample().split("\n")) { + out.append(" * "); + out.append(elem); + out.append("\n"); + } + out.append(" * ```\n"); + } + out.append(" */\n"); + } + return out.toString(); + } + + public String optionalTypeZod(final FieldProperty field) { + // Common checking element (apply to List, Map, ...) + if (field.nullable()) { + return ".optional()"; + } + if (field.notNull()) { + return ""; + } + // Other object: + if (field.model().getOriginClasses() == null || field.model().getOriginClasses().isPrimitive()) { + return ""; + } + if (field.columnNotNull()) { + return ""; + } + return ".optional()"; + } + + public String maxSizeZod(final FieldProperty field) { + final StringBuilder builder = new StringBuilder(); + final Class clazz = field.model().getOriginClasses(); + if (clazz == String.class) { + if (field.sizeMin() > 0) { + builder.append(".min("); + builder.append(field.sizeMin()); + builder.append(")"); + } + if (field.sizeMax() > 0) { + builder.append(".max("); + builder.append(field.sizeMax()); + builder.append(")"); + } + } + if (clazz == short.class || clazz == Short.class || clazz == int.class || clazz == Integer.class + || clazz == long.class || clazz == Long.class || clazz == float.class || clazz == Float.class + || clazz == double.class || clazz == Double.class) { + if (field.min() != null && field.min() > 0) { + builder.append(".min("); + builder.append(field.min()); + builder.append(")"); + } + if (field.max() != null && field.max() > 0) { + builder.append(".max("); + builder.append(field.max()); + builder.append(")"); + } + } + return builder.toString(); + } + + public String readOnlyZod(final FieldProperty field) { + if (field.readOnly()) { + return ".readonly()"; + } + return ""; + } + + public String generateBaseObject() { + final StringBuilder out = new StringBuilder(); + return out.toString(); + } + + public String convertHtml(final String data) { + return data.replace("<", "<").replace(">", ">"); + } + + public String generateObject(final ClassObjectModel model, final DotClassElementGroup dotGroup) throws IOException { + final StringBuilder out = new StringBuilder(); + final StringBuilder outLinks = new StringBuilder(); + out.append(""" + %s [ + shape=plain + ranksep="2" + label=< + + + + + + +
%s
+ + """.formatted(this.dotTypeName, this.dotTypeName)); + String inheritence = null; + if (model.getExtendsClass() != null) { + final ClassModel parentClass = model.getExtendsClass(); + final DotClassElement dotParentModel = dotGroup.find(parentClass); + inheritence = dotParentModel.dotTypeName; + } + if (model.getFields().size() == 0) { + out.append("\t\t\t\t\t\t"); + } + for (final FieldProperty field : model.getFields()) { + final ClassModel fieldModel = field.model(); + if (field.comment() != null) { + out.append("\t\t\t\t\t\t\n"); + } + out.append("\t\t\t\t\t\t\n"); + } + out.append(""" +
(empty)
// "); + out.append(convertHtml(field.comment())); + out.append("
+ "); + out.append(field.name()); + out.append(": "); + + if (fieldModel instanceof ClassEnumModel) { + final DotClassElement dotFieldModel = dotGroup.find(fieldModel); + out.append(dotFieldModel.dotTypeName); + outLinks.append("\t"); + outLinks.append(this.dotTypeName); + outLinks.append(":"); + outLinks.append(field.name()); + outLinks.append(":e -> "); + outLinks.append(dotFieldModel.dotTypeName); + outLinks.append(":NAME:w\n"); + } else if (fieldModel instanceof ClassObjectModel) { + final DotClassElement dotFieldModel = dotGroup.find(fieldModel); + out.append(dotFieldModel.dotTypeName); + if (dotFieldModel.nativeType == DefinedPosition.NORMAL) { + outLinks.append(this.dotTypeName); + outLinks.append(":"); + outLinks.append(field.name()); + outLinks.append(":e -> "); + outLinks.append(dotFieldModel.dotTypeName); + outLinks.append(":NAME:w\n"); + } + } else if (fieldModel instanceof final ClassListModel fieldListModel) { + final String data = generateDotList(fieldListModel, dotGroup); + out.append(data); + final String className = generateDotListClassName(fieldListModel, dotGroup); + if (className != null) { + outLinks.append(this.dotTypeName); + outLinks.append(":"); + outLinks.append(field.name()); + outLinks.append(":e -> "); + outLinks.append(className); + outLinks.append(":NAME:w\n"); + } + } else if (fieldModel instanceof final ClassMapModel fieldMapModel) { + final String data = generateDotMap(fieldMapModel, dotGroup); + out.append(data); + final String className = generateDotMapClassName(fieldMapModel, dotGroup); + if (className != null) { + outLinks.append(this.dotTypeName); + outLinks.append(":"); + outLinks.append(field.name()); + outLinks.append(":e -> "); + outLinks.append(className); + outLinks.append(":NAME:w\n"); + } + } /* + out.append(maxSizeZod(field)); + out.append(readOnlyZod(field)); + out.append(optionalTypeZod(field)); + out.append(",\n"); + */ + out.append("
+
> + ] + """); + if (inheritence != null) { + out.append("\tedge [dir=back arrowtail=empty arrowsize=2]\n"); + out.append("\t"); + out.append(inheritence); + // heritage stop link on the "s" South + out.append(":s -> "); + out.append(this.dotTypeName); + // heritage start link on the "n" North + out.append(":n\n"); + } + if (!outLinks.isEmpty()) { + out.append("\tedge [dir=back arrowtail=diamond arrowsize=2]\n"); + //out.append("\tedge [arrowhead=diamond arrowsize=2]\n"); + out.append(outLinks.toString()); + + } + return out.toString(); + + } + + private static String generateDotMap(final ClassMapModel model, final DotClassElementGroup dotGroup) { + final StringBuilder out = new StringBuilder(); + out.append("Map<"); + if (model.keyModel instanceof final ClassListModel fieldListModel) { + final String tmp = generateDotList(fieldListModel, dotGroup); + out.append(tmp); + } else if (model.keyModel instanceof final ClassMapModel fieldMapModel) { + final String tmp = generateDotMap(fieldMapModel, dotGroup); + out.append(tmp); + } else if (model.keyModel instanceof final ClassObjectModel fieldObjectModel) { + final String tmp = generateDotObject(fieldObjectModel, dotGroup); + out.append(tmp); + } else if (model.keyModel instanceof final ClassEnumModel fieldEnumModel) { + final String tmp = generateDotEnum(fieldEnumModel, dotGroup); + out.append(tmp); + } + out.append(", "); + if (model.valueModel instanceof final ClassListModel fieldListModel) { + final String tmp = generateDotList(fieldListModel, dotGroup); + out.append(tmp); + } else if (model.valueModel instanceof final ClassMapModel fieldMapModel) { + final String tmp = generateDotMap(fieldMapModel, dotGroup); + out.append(tmp); + } else if (model.valueModel instanceof final ClassObjectModel fieldObjectModel) { + final String tmp = generateDotObject(fieldObjectModel, dotGroup); + out.append(tmp); + } else if (model.valueModel instanceof final ClassEnumModel fieldEnumModel) { + final String tmp = generateDotEnum(fieldEnumModel, dotGroup); + out.append(tmp); + } + out.append(">"); + return out.toString(); + } + + private static String generateDotEnum(final ClassEnumModel model, final DotClassElementGroup dotGroup) { + final DotClassElement dotParentModel = dotGroup.find(model); + return dotParentModel.dotTypeName; + } + + private static String generateDotObject(final ClassObjectModel model, final DotClassElementGroup dotGroup) { + final DotClassElement dotParentModel = dotGroup.find(model); + return dotParentModel.dotTypeName; + } + + private static String generateDotObjectClassName( + final ClassObjectModel model, + final DotClassElementGroup dotGroup) { + final DotClassElement dotParentModel = dotGroup.find(model); + if (dotParentModel.nativeType == DefinedPosition.NORMAL) { + return dotParentModel.dotTypeName; + } + return null; + } + + private static String generateDotListClassName(final ClassListModel model, final DotClassElementGroup dotGroup) { + if (model.valueModel instanceof final ClassListModel fieldListModel) { + return generateDotListClassName(fieldListModel, dotGroup); + } else if (model.valueModel instanceof final ClassMapModel fieldMapModel) { + return generateDotMapClassName(fieldMapModel, dotGroup); + } else if (model.valueModel instanceof final ClassObjectModel fieldObjectModel) { + return generateDotObjectClassName(fieldObjectModel, dotGroup); + } + return null; + } + + private static String generateDotMapClassName(final ClassMapModel model, final DotClassElementGroup dotGroup) { + if (model.valueModel instanceof final ClassListModel fieldListModel) { + return generateDotListClassName(fieldListModel, dotGroup); + } else if (model.valueModel instanceof final ClassMapModel fieldMapModel) { + return generateDotMapClassName(fieldMapModel, dotGroup); + } else if (model.valueModel instanceof final ClassObjectModel fieldObjectModel) { + return generateDotObjectClassName(fieldObjectModel, dotGroup); + } else if (model.valueModel instanceof final ClassEnumModel fieldEnumModel) { + return generateDotEnum(fieldEnumModel, dotGroup); + } + return null; + } + + private static String generateDotList(final ClassListModel model, final DotClassElementGroup dotGroup) { + final StringBuilder out = new StringBuilder(); + out.append("List<"); + if (model.valueModel instanceof final ClassListModel fieldListModel) { + final String tmp = generateDotList(fieldListModel, dotGroup); + out.append(tmp); + } else if (model.valueModel instanceof final ClassMapModel fieldMapModel) { + final String tmp = generateDotMap(fieldMapModel, dotGroup); + out.append(tmp); + } else if (model.valueModel instanceof final ClassObjectModel fieldObjectModel) { + final String tmp = generateDotObject(fieldObjectModel, dotGroup); + out.append(tmp); + } + out.append(">"); + return out.toString(); + } + + public String generateFile(final DotClassElementGroup dotGroup) throws IOException { + if (this.nativeType == DefinedPosition.NATIVE) { + return ""; + } + final ClassModel model = this.models.get(0); + String data = ""; + if (this.nativeType == DefinedPosition.BASIC && model instanceof ClassObjectModel) { + // nothing to do___ data = generateBaseObject(); + } else if (model instanceof final ClassEnumModel modelEnum) { + data = generateEnum(modelEnum, dotGroup); + } else if (model instanceof final ClassObjectModel modelObject) { + data = generateObject(modelObject, dotGroup); + } + return data; + } + + private static String generateLocalModelBase(final ClassModel model, final DotClassElementGroup dotGroup) + throws IOException { + if (model instanceof final ClassObjectModel objectModel) { + return generateDotObject(objectModel, dotGroup); + } + if (model instanceof final ClassEnumModel enumModel) { + return generateDotEnum(enumModel, dotGroup); + } + if (model instanceof final ClassListModel listModel) { + return generateDotList(listModel, dotGroup); + } + if (model instanceof final ClassMapModel mapModel) { + return generateDotMap(mapModel, dotGroup); + } + return ""; + } + + public static String generateLocalModel( + final String ModelName, + final List models, + final DotClassElementGroup dotGroup) throws IOException { + if (models.size() == 1) { + if (models.get(0) instanceof ClassObjectModel) { + return null; + } + if (models.get(0) instanceof ClassEnumModel) { + return null; + } + } + final StringBuilder out = new StringBuilder(); + if (models.size() == 1) { + out.append(generateLocalModelBase(models.get(0), dotGroup)); + } else { + out.append("Union<"); + for (final ClassModel model : models) { + out.append("\t"); + out.append(generateLocalModelBase(models.get(0), dotGroup)); + out.append(",\n"); + } + out.append(">"); + } + return out.toString(); + } + +} \ No newline at end of file diff --git a/src/org/kar/archidata/externalRestApi/dot/DotClassElementGroup.java b/src/org/kar/archidata/externalRestApi/dot/DotClassElementGroup.java new file mode 100644 index 0000000..b7ebab0 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/dot/DotClassElementGroup.java @@ -0,0 +1,27 @@ +package org.kar.archidata.externalRestApi.dot; + +import java.util.List; + +import org.kar.archidata.externalRestApi.model.ClassModel; + +public class DotClassElementGroup { + private final List dotElements; + + public List getDotElements() { + return this.dotElements; + } + + public DotClassElementGroup(final List tsElements) { + this.dotElements = tsElements; + } + + public DotClassElement find(final ClassModel model) { + for (final DotClassElement elem : this.dotElements) { + if (elem.isCompatible(model)) { + return elem; + } + } + return null; + } + +}