From cb9e16dbf37295fbc1d32b8d378d7e27f5104bd2 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 12 Aug 2022 14:08:46 +0200 Subject: [PATCH 01/39] #860 template structure and sql util class --- .../cobigen-eclipse-test/.classpath | 4 +-- cobigen-templates/sql-openapi-app/context.xml | 13 ++++++++++ .../sql-openapi-app/templates.xml | 0 .../templates/devon4j/utils/SQLUtil.java | 25 +++++++++++++++++++ .../main/templates/sql_java_app/context.xml | 13 ++++++++++ .../main/templates/sql_java_app/templates.xml | 21 ++++++++++++++++ .../${variables.entityName}Entity.sql.ftl | 23 +++++++++++++++++ 7 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 cobigen-templates/sql-openapi-app/context.xml create mode 100644 cobigen-templates/sql-openapi-app/templates.xml create mode 100644 cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java create mode 100644 cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/context.xml create mode 100644 cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml create mode 100644 cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/${variables.entityName}Entity.sql.ftl diff --git a/cobigen-eclipse/cobigen-eclipse-test/.classpath b/cobigen-eclipse/cobigen-eclipse-test/.classpath index 8d0ae98bfc..831f611b54 100644 --- a/cobigen-eclipse/cobigen-eclipse-test/.classpath +++ b/cobigen-eclipse/cobigen-eclipse-test/.classpath @@ -1,7 +1,5 @@ - - @@ -32,5 +30,7 @@ + + diff --git a/cobigen-templates/sql-openapi-app/context.xml b/cobigen-templates/sql-openapi-app/context.xml new file mode 100644 index 0000000000..be138dda6d --- /dev/null +++ b/cobigen-templates/sql-openapi-app/context.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/cobigen-templates/sql-openapi-app/templates.xml b/cobigen-templates/sql-openapi-app/templates.xml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java new file mode 100644 index 0000000000..4b0120a869 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -0,0 +1,25 @@ +package com.devonfw.cobigen.templates.devon4j.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides operations to identify and process SQL specific information + * + */ +public class SQLUtil { + + /** + * Logger for this class + */ + private static final Logger LOG = LoggerFactory.getLogger(JavaUtil.class); + + /** + * The constructor. + */ + public SQLUtil() { + + // Empty for CobiGen to automatically instantiate it + } + +} diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/context.xml b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/context.xml new file mode 100644 index 0000000000..7dc46c7425 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/context.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml new file mode 100644 index 0000000000..44e8feb936 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/${variables.entityName}Entity.sql.ftl new file mode 100644 index 0000000000..eb8f77623d --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/${variables.entityName}Entity.sql.ftl @@ -0,0 +1,23 @@ +CREATE TABLE ${variables.entityName?upper_case} ( + ID DECIMAL(10,0), + VERSION INTEGER NOT NULL, +<#list pojo.fields as field> +<#if !field.type?starts_with("List<") && !field.type?starts_with("Set<")> + <#if field.type?contains("Entity") || field.type=='long' || field.type=='java.lang.Long'> + <#assign type = 'DECIMAL(10,0)'> + <#elseif field.type=='int' || field.type=='java.lang.Integer' || field.type=='java.time.Year' || field.type=='java.time.Month'> + <#assign type = 'INTEGER'> + <#elseif field.type=='java.time.Instant' || field.type=='java.sql.Timestamp'> + <#assign type = 'TIMESTAMP'> + <#elseif field.type=='java.util.Date'> + <#assign type = 'DATE'> + <#elseif field.type=='boolean'> + <#assign type = 'NUMBER(1) DEFAULT 0'> + <#else> + <#assign type = 'VARCHAR2(255 CHAR)'> + + ${field.name?upper_case?right_pad(30)}${type}, + + + CONSTRAINT PK_${variables.entityName?upper_case} PRIMARY KEY(ID) +) \ No newline at end of file From c01b3f2ed92b8013bf4e771fdd30f150e4f52cac Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 12 Aug 2022 16:46:07 +0200 Subject: [PATCH 02/39] Refactored Util classes, implemented sql util functions --- .../templates/devon4j/utils/CommonUtil.java | 45 +++++ .../templates/devon4j/utils/JavaUtil.java | 30 +--- .../templates/devon4j/utils/SQLUtil.java | 155 +++++++++++++++++- 3 files changed, 201 insertions(+), 29 deletions(-) create mode 100644 cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java new file mode 100644 index 0000000000..1743a9b06d --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java @@ -0,0 +1,45 @@ +package com.devonfw.cobigen.templates.devon4j.utils; + +import java.lang.reflect.Field; +import java.util.Collection; + +/** + * This class provides utility for all objects that inherit from it + * + */ +public class CommonUtil { + + /** + * The constructor. + */ + public CommonUtil() { + + // Empty for CobiGen to automatically instantiate it + } + + /** + * @param pojoClass {@link Class} the class object of the pojo + * @param fieldName {@link String} the name of the field + * @return true if the field is an instance of java.utils.Collections + * @throws NoSuchFieldException indicating something awefully wrong in the used model + * @throws SecurityException if the field cannot be accessed. + */ + public boolean isCollection(Class pojoClass, String fieldName) throws NoSuchFieldException, SecurityException { + + if (pojoClass == null) { + return false; + } + + Field field = pojoClass.getDeclaredField(fieldName); + if (field == null) { + field = pojoClass.getField(fieldName); + } + if (field == null) { + return false; + } else { + return Collection.class.isAssignableFrom(field.getType()); + } + + } + +} diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java index ced2f5fde2..62b633fa36 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java @@ -3,7 +3,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.Collection; import java.util.Map; import org.apache.commons.lang3.ClassUtils; @@ -14,7 +13,7 @@ * Provides type operations, mainly checks and casts for Java Primitives, to be used in the templates * */ -public class JavaUtil { +public class JavaUtil extends CommonUtil { /** * Logger for this class @@ -31,7 +30,7 @@ public JavaUtil() { /** * Returns the Object version of a Java primitive or the input if the input isn't a java primitive - * + * * @param simpleType String * @return the corresponding object wrapper type simple name of the input if the input is the name of a primitive java * type. The input itself if not. (e.g. "int" results in "Integer") @@ -215,31 +214,6 @@ public String castJavaPrimitives(Class pojoClass, String fieldName) } } - /** - * @param pojoClass {@link Class} the class object of the pojo - * @param fieldName {@link String} the name of the field - * @return true if the field is an instance of java.utils.Collections - * @throws NoSuchFieldException indicating something awefully wrong in the used model - * @throws SecurityException if the field cannot be accessed. - */ - public boolean isCollection(Class pojoClass, String fieldName) throws NoSuchFieldException, SecurityException { - - if (pojoClass == null) { - return false; - } - - Field field = pojoClass.getDeclaredField(fieldName); - if (field == null) { - field = pojoClass.getField(fieldName); - } - if (field == null) { - return false; - } else { - return Collection.class.isAssignableFrom(field.getType()); - } - - } - /** * Returns the Ext Type to a given java type * diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 4b0120a869..7b5085d489 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -1,5 +1,17 @@ package com.devonfw.cobigen.templates.devon4j.utils; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -7,7 +19,7 @@ * Provides operations to identify and process SQL specific information * */ -public class SQLUtil { +public class SQLUtil extends CommonUtil { /** * Logger for this class @@ -22,4 +34,145 @@ public SQLUtil() { // Empty for CobiGen to automatically instantiate it } + /** + * Helper methods to get all fields recursively including fields from super classes + * + * @param fields list of fields to be accumulated during recursion + * @param cl class to find fields + * @return list of all fields + */ + private static List getAllFields(List fields, Class cl) { + + fields.addAll(Arrays.asList(cl.getDeclaredFields())); + + if (cl.getSuperclass() != null) { + getAllFields(fields, cl.getSuperclass()); + } + + return fields; + } + + /** + * Helper method to retrieve the type of a field including fields from super classes + * + * @param pojoClass {@link Class} the class object of the pojo + * @param fieldName {@link String} the name of the field + * @return return the type of the field + */ + private Class getTypeOfField(Class pojoClass, String fieldName) { + + if (pojoClass != null) { + // automatically fetches all fields from pojoClass including its super classes + List fields = new ArrayList<>(); + getAllFields(fields, pojoClass); + + Optional field = fields.stream().filter(f -> f.getName().equals(fieldName)).findFirst(); + + if (field.isPresent()) { + return field.get().getType(); + } + } + LOG.error("Could not find type of field {}", fieldName); + return null; + } + + /** + * Get the annotated table name of a given Entity class + * + * @param className {@link String} full qualified class name + * @return return the annotated table name if + * @throws ClassNotFoundException + */ + @SuppressWarnings("javadoc") + public String getEntityTableName(String className) throws ClassNotFoundException { + + if (!className.endsWith("Entity")) { + LOG.error("Could not return table name because {} is not an Entity class", className); + return null; + } + + try { + Class entityClass = Class.forName(className); + Table table = entityClass.getAnnotation(Table.class); + return table == null + ? StringUtils.left(entityClass.getSimpleName(), entityClass.getSimpleName().length() - "Entity".length()) + : table.name(); + } catch (ClassNotFoundException e) { + LOG.error("{}: Could not find {}", e.getMessage(), className); + return null; + } + } + + /** + * @param className {@link String} full qualified class name + * @param fieldName {@link String} the name of the field + * @return type of the field in the string + * @throws ClassNotFoundException + */ + @SuppressWarnings("javadoc") + public String getCanonicalNameOfFieldType(String className, String fieldName) throws ClassNotFoundException { + + try { + Class entityClass = Class.forName(className); + Class type = getTypeOfField(entityClass, fieldName); + if (type != null) { + return type.getCanonicalName(); + } + } catch (ClassNotFoundException e) { + LOG.error("{}: Could not find {}", e.getMessage(), className); + } + return null; + } + + /** + * Get the primary key and its type of a given Entity class + * + * @param className {@link String} full qualified class name + * @return the primary key and its type if found or null + */ + public String getPrimaryKey(String className) { + + try { + Class entityClass = Class.forName(className); + List fields = new ArrayList<>(); + getAllFields(fields, entityClass); + for (Field field : fields) { + if (field.isAnnotationPresent(Id.class)) { + return field.getType().getCanonicalName() + "," + + (field.isAnnotationPresent(Column.class) ? field.getAnnotation(Column.class).name() : field.getName()); + } else { + Optional getterOptional = Arrays.stream(entityClass.getMethods()) + .filter(m -> m.getName() + .equals("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1)) + && m.isAnnotationPresent(Id.class)) + .findFirst(); + if (getterOptional.isPresent()) { + Method getter = getterOptional.get(); + return getter.getReturnType().getCanonicalName() + "," + + (getter.isAnnotationPresent(Column.class) ? getter.getAnnotation(Column.class).name() + : field.getName()); + } + } + } + } catch (ClassNotFoundException e) { + LOG.error("{}: Could not find {}", e.getMessage(), className); + } + LOG.error("Could not find the field or getter with @Id annotated"); + return null; + } + + // public String getSqlType(String className, String fieldName) throws ClassNotFoundException { + // + // try { + // String javaType = getCanonicalNameOfFieldType(className, fieldName); + // } catch (ClassNotFoundException e) { + // LOG.error("{}: Could not find {}", e.getMessage(), className); + // } + // return ""; + // } + // + // public String getSqlStrategy(Field field) { + // + // return ""; + // } } From d560494fae9db86c06f8709f4654046c660c2ee8 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Thu, 18 Aug 2022 11:40:25 +0200 Subject: [PATCH 03/39] removed .classpath from tracking --- cobigen-templates/templates-devon4j/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 cobigen-templates/templates-devon4j/.gitignore diff --git a/cobigen-templates/templates-devon4j/.gitignore b/cobigen-templates/templates-devon4j/.gitignore new file mode 100644 index 0000000000..a2aebfea6f --- /dev/null +++ b/cobigen-templates/templates-devon4j/.gitignore @@ -0,0 +1 @@ +.classpath \ No newline at end of file From e0f1759270ceabb0d597d0f9ac2165885d412af6 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Thu, 18 Aug 2022 14:06:35 +0200 Subject: [PATCH 04/39] Helper function mapJavaToSqlType(String canonicalTypeName) implemented --- .../templates/devon4j/utils/CommonUtil.java | 25 ++++ .../templates/devon4j/utils/JavaUtil.java | 23 ---- .../templates/devon4j/utils/SQLUtil.java | 112 +++++++++++++++--- 3 files changed, 119 insertions(+), 41 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java index 1743a9b06d..af5e7ad7a9 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java @@ -3,6 +3,10 @@ import java.lang.reflect.Field; import java.util.Collection; +import org.apache.commons.lang3.ClassUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * This class provides utility for all objects that inherit from it * @@ -17,6 +21,27 @@ public CommonUtil() { // Empty for CobiGen to automatically instantiate it } + /** + * Logger for this class + */ + protected static final Logger LOG = LoggerFactory.getLogger(JavaUtil.class); + + /** + * Checks whether the class given by the full qualified name is an enum + * + * @param className full qualified class name + * @return true if the class is an enum, false otherwise + */ + public boolean isEnum(String className) { + + try { + return ClassUtils.getClass(className).isEnum(); + } catch (ClassNotFoundException e) { + LOG.warn("{}: Could not find {}", e.getMessage(), className); + return false; + } + } + /** * @param pojoClass {@link Class} the class object of the pojo * @param fieldName {@link String} the name of the field diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java index 62b633fa36..943c0bf66b 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java @@ -6,8 +6,6 @@ import java.util.Map; import org.apache.commons.lang3.ClassUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Provides type operations, mainly checks and casts for Java Primitives, to be used in the templates @@ -15,11 +13,6 @@ */ public class JavaUtil extends CommonUtil { - /** - * Logger for this class - */ - private static final Logger LOG = LoggerFactory.getLogger(JavaUtil.class); - /** * The constructor. */ @@ -398,22 +391,6 @@ private Method findMethod(Class pojoClass, String methodName) { return null; } - /** - * Checks whether the class given by the full qualified name is an enum - * - * @param className full qualified class name - * @return true if the class is an enum, false otherwise - */ - public boolean isEnum(String className) { - - try { - return ClassUtils.getClass(className).isEnum(); - } catch (ClassNotFoundException e) { - LOG.warn("{}: Could not find {}", e.getMessage(), className); - return false; - } - } - /** * Returns the first enum value of an enum class * diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 7b5085d489..324157a7af 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -12,8 +12,6 @@ import javax.persistence.Table; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Provides operations to identify and process SQL specific information @@ -21,11 +19,6 @@ */ public class SQLUtil extends CommonUtil { - /** - * Logger for this class - */ - private static final Logger LOG = LoggerFactory.getLogger(JavaUtil.class); - /** * The constructor. */ @@ -161,18 +154,101 @@ public String getPrimaryKey(String className) { return null; } - // public String getSqlType(String className, String fieldName) throws ClassNotFoundException { - // - // try { - // String javaType = getCanonicalNameOfFieldType(className, fieldName); - // } catch (ClassNotFoundException e) { - // LOG.error("{}: Could not find {}", e.getMessage(), className); - // } - // return ""; - // } - // - // public String getSqlStrategy(Field field) { + // /** + // * + // * @param field + // * @return + // */ + // public String getSqlType(Field field) { // // return ""; // } + + /** + * Helper function to map a Java Type to its equivalent SQL type + * + * @param canonicalTypeName {@link String} full qualified class name + * @return returns the equivalent SQL type + */ + private String mapJavaToSqlType(String canonicalTypeName) { + + if (isEnum(canonicalTypeName)) { + return "INTEGER"; + } + // JavaUtil.isEnum(canonicalTypeName) == INTEGER + switch (canonicalTypeName) { + // INTEGER + case "Integer": + return "INTEGER"; + case "int": + return "INTEGER"; + case "Year": + return "INTEGER"; + case "Month": + return "INTEGER"; + // BIGINT + case "Long": + return "BIGINT"; + case "long": + return "BIGINT"; + case "Object": + return "BIGINT"; + // SMALLINT + case "Short": + return "SMALLINT"; + case "short": + return "SMALLINT"; + // FLOAT + case "Float": + return "FLOAT"; + case "float": + return "FLOAT"; + // DOUBLE + case "Double": + return "DOUBLE"; + case "double": + return "DOUBLE"; + // NUMERIC + case "BigDecimal": + return "NUMERIC"; + case "BigInteger": + return "NUMERIC"; + // CHAR + case "Character": + return "CHAR"; + case "char": + return "CHAR"; + // TINYINT + case "Byte": + return "TINYINT"; + case "byte": + return "TINYINT"; + // BOOLEAN + case "Boolean": + return "BOOLEAN"; + case "boolean": + return "BOOLEAN"; + // TIMESTAMP + case "Instant": + return "TIMESTAMP"; + case "Timestamp": + return "TIMESTAMP"; + // DATE + case "Date": + return "DATE"; + case "Calendar": + return "DATE"; + // TIME + case "Time": + return "TIME"; + // BINARY + case "UUID": + return "BINARY"; + // BLOB + case "Blob": + return "BLOB"; + default: + return "VARCHAR"; + } + } } From feaca19ecbbfc6c9a95648cd76ace554f4472a39 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Thu, 18 Aug 2022 17:52:19 +0200 Subject: [PATCH 05/39] Added a function that allows to retrieve annotations --- .../templates/devon4j/utils/SQLUtil.java | 244 +++++++++++------- 1 file changed, 149 insertions(+), 95 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 324157a7af..f6ed799d8b 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -1,5 +1,6 @@ package com.devonfw.cobigen.templates.devon4j.utils; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; @@ -27,6 +28,101 @@ public SQLUtil() { // Empty for CobiGen to automatically instantiate it } + /** + * Helper function to map a Java Type to its equivalent SQL type + * + * @param canonicalTypeName {@link String} full qualified class name + * @return returns the equivalent SQL type + */ + private String mapJavaToSqlType(String canonicalTypeName) throws IllegalArgumentException { + + try { + + if (isEnum(canonicalTypeName)) { + return "INTEGER"; + } + + switch (canonicalTypeName) { + // INTEGER + case "Integer": + return "INTEGER"; + case "int": + return "INTEGER"; + case "Year": + return "INTEGER"; + case "Month": + return "INTEGER"; + // BIGINT + case "Long": + return "BIGINT"; + case "long": + return "BIGINT"; + case "Object": + return "BIGINT"; + // SMALLINT + case "Short": + return "SMALLINT"; + case "short": + return "SMALLINT"; + // FLOAT + case "Float": + return "FLOAT"; + case "float": + return "FLOAT"; + // DOUBLE + case "Double": + return "DOUBLE"; + case "double": + return "DOUBLE"; + // NUMERIC + case "BigDecimal": + return "NUMERIC"; + case "BigInteger": + return "NUMERIC"; + // CHAR + case "Character": + return "CHAR"; + case "char": + return "CHAR"; + // TINYINT + case "Byte": + return "TINYINT"; + case "byte": + return "TINYINT"; + // BOOLEAN + case "Boolean": + return "BOOLEAN"; + case "boolean": + return "BOOLEAN"; + // TIMESTAMP + case "Instant": + return "TIMESTAMP"; + case "Timestamp": + return "TIMESTAMP"; + // DATE + case "Date": + return "DATE"; + case "Calendar": + return "DATE"; + // TIME + case "Time": + return "TIME"; + // BINARY + case "UUID": + return "BINARY"; + // BLOB + case "Blob": + return "BLOB"; + default: + return "VARCHAR"; + } + } catch (IllegalArgumentException e) { + LOG.error("{}: The parameter is not a valid argument", e.getMessage()); + return null; + } + + } + /** * Helper methods to get all fields recursively including fields from super classes * @@ -46,13 +142,13 @@ private static List getAllFields(List fields, Class cl) { } /** - * Helper method to retrieve the type of a field including fields from super classes + * Helper method to get a field of a pojo class by its name. Including fields from super classes * * @param pojoClass {@link Class} the class object of the pojo * @param fieldName {@link String} the name of the field - * @return return the type of the field + * @return return the field object throws IllegalArgumentException */ - private Class getTypeOfField(Class pojoClass, String fieldName) { + private Field getFieldByName(Class pojoClass, String fieldName) throws IllegalArgumentException { if (pojoClass != null) { // automatically fetches all fields from pojoClass including its super classes @@ -62,13 +158,47 @@ private Class getTypeOfField(Class pojoClass, String fieldName) { Optional field = fields.stream().filter(f -> f.getName().equals(fieldName)).findFirst(); if (field.isPresent()) { - return field.get().getType(); + return field.get(); } } LOG.error("Could not find type of field {}", fieldName); return null; } + /** + * Method to retrieve the type of a field + * + * @param pojoClass {@link Class} the class object of the pojo + * @param fieldName {@link String} the name of the field + * @return return the type of the field + */ + public Class getTypeOfField(Class pojoClass, String fieldName) { + + if (pojoClass != null) { + Field field = getFieldByName(pojoClass, fieldName); + return field.getType(); + } + return null; + } + + /** + * Method to retrieve the annotations of a field + * + * @param pojoClass {@link Class} the class object of the pojo + * @param fieldName {@link String} the name of the field + * @return an array with annotations found (length = 0 if now annotations found) + */ + public Annotation[] getFieldAnnotations(Class pojoClass, String fieldName) { + + if (pojoClass != null) { + Annotation[] annotations; + Field field = getFieldByName(pojoClass, fieldName); + annotations = field.getAnnotations(); + return annotations; + } + return null; + } + /** * Get the annotated table name of a given Entity class * @@ -154,101 +284,25 @@ public String getPrimaryKey(String className) { return null; } - // /** - // * - // * @param field - // * @return - // */ - // public String getSqlType(Field field) { - // - // return ""; - // } - /** - * Helper function to map a Java Type to its equivalent SQL type * - * @param canonicalTypeName {@link String} full qualified class name - * @return returns the equivalent SQL type + * @param className {@link String} full qualified class name + * @param fieldName {@link String} the name of the field + * @return + * @throws ClassNotFoundException */ - private String mapJavaToSqlType(String canonicalTypeName) { + public String getSqlType(String className, String fieldName) throws ClassNotFoundException { - if (isEnum(canonicalTypeName)) { - return "INTEGER"; - } - // JavaUtil.isEnum(canonicalTypeName) == INTEGER - switch (canonicalTypeName) { - // INTEGER - case "Integer": - return "INTEGER"; - case "int": - return "INTEGER"; - case "Year": - return "INTEGER"; - case "Month": - return "INTEGER"; - // BIGINT - case "Long": - return "BIGINT"; - case "long": - return "BIGINT"; - case "Object": - return "BIGINT"; - // SMALLINT - case "Short": - return "SMALLINT"; - case "short": - return "SMALLINT"; - // FLOAT - case "Float": - return "FLOAT"; - case "float": - return "FLOAT"; - // DOUBLE - case "Double": - return "DOUBLE"; - case "double": - return "DOUBLE"; - // NUMERIC - case "BigDecimal": - return "NUMERIC"; - case "BigInteger": - return "NUMERIC"; - // CHAR - case "Character": - return "CHAR"; - case "char": - return "CHAR"; - // TINYINT - case "Byte": - return "TINYINT"; - case "byte": - return "TINYINT"; - // BOOLEAN - case "Boolean": - return "BOOLEAN"; - case "boolean": - return "BOOLEAN"; - // TIMESTAMP - case "Instant": - return "TIMESTAMP"; - case "Timestamp": - return "TIMESTAMP"; - // DATE - case "Date": - return "DATE"; - case "Calendar": - return "DATE"; - // TIME - case "Time": - return "TIME"; - // BINARY - case "UUID": - return "BINARY"; - // BLOB - case "Blob": - return "BLOB"; - default: - return "VARCHAR"; + try { + String fieldType = getCanonicalNameOfFieldType(className, fieldName); + String sqlType = mapJavaToSqlType(fieldType); + Class entityClass = Class.forName(className); + Annotation[] annotations = getFieldAnnotations(entityClass, fieldName); + return sqlType; + } catch (ClassNotFoundException e) { + LOG.error("{}: Could not find {}", e.getMessage(), className); + return null; } } + } From 49fa75e1f42a417649526a00bd4e6f9500f62448 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Thu, 18 Aug 2022 19:04:44 +0200 Subject: [PATCH 06/39] Added a new dependency for SQL specific annotations, almost finished getSqlType --- cobigen-templates/templates-devon4j/pom.xml | 5 +++++ .../templates/devon4j/utils/SQLUtil.java | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/cobigen-templates/templates-devon4j/pom.xml b/cobigen-templates/templates-devon4j/pom.xml index 9bcaece44e..c20e74ecb4 100644 --- a/cobigen-templates/templates-devon4j/pom.xml +++ b/cobigen-templates/templates-devon4j/pom.xml @@ -54,6 +54,11 @@ 3.10.0 test + + jakarta.validation + jakarta.validation-api + 3.0.2 + diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index f6ed799d8b..76c19f0f2c 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -285,10 +285,11 @@ public String getPrimaryKey(String className) { } /** + * Method to get the SQL type based on annotations and field type * * @param className {@link String} full qualified class name * @param fieldName {@link String} the name of the field - * @return + * @return SQL type as a String * @throws ClassNotFoundException */ public String getSqlType(String className, String fieldName) throws ClassNotFoundException { @@ -298,7 +299,20 @@ public String getSqlType(String className, String fieldName) throws ClassNotFoun String sqlType = mapJavaToSqlType(fieldType); Class entityClass = Class.forName(className); Annotation[] annotations = getFieldAnnotations(entityClass, fieldName); - return sqlType; + String sqlTypeExtension = ""; + + if (annotations.length != 0) { + for (Annotation annotation : annotations) { + // if (annotation.annotationType().equals(Constraint.class.getAnnotationsByType(Size.class))) { + // sqlTypeExtension = sqlTypeExtension + ""; + // } + // if (annotation.annotationType().isInstance(Column.class) && annotation.annotationType().get ) { + // Column column = annotation.annotationType().get + // + // } + } + } + return sqlType + sqlTypeExtension; } catch (ClassNotFoundException e) { LOG.error("{}: Could not find {}", e.getMessage(), className); return null; From 609680b061c9f0c3b7a854a3c134ac3bf5986022 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 19 Aug 2022 14:17:52 +0200 Subject: [PATCH 07/39] Implemented Varchar, Not Null and Auto Increment annotation --- .../templates/devon4j/utils/SQLUtil.java | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 76c19f0f2c..9f861ee31e 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -9,11 +9,15 @@ import java.util.Optional; import javax.persistence.Column; +import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.apache.commons.lang3.StringUtils; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + /** * Provides operations to identify and process SQL specific information * @@ -186,7 +190,7 @@ public Class getTypeOfField(Class pojoClass, String fieldName) { * * @param pojoClass {@link Class} the class object of the pojo * @param fieldName {@link String} the name of the field - * @return an array with annotations found (length = 0 if now annotations found) + * @return an array with annotations found (length = 0 if now annotations were found) */ public Annotation[] getFieldAnnotations(Class pojoClass, String fieldName) { @@ -292,24 +296,30 @@ public String getPrimaryKey(String className) { * @return SQL type as a String * @throws ClassNotFoundException */ + @SuppressWarnings("javadoc") public String getSqlType(String className, String fieldName) throws ClassNotFoundException { try { - String fieldType = getCanonicalNameOfFieldType(className, fieldName); - String sqlType = mapJavaToSqlType(fieldType); + String sqlType = mapJavaToSqlType(getCanonicalNameOfFieldType(className, fieldName)); Class entityClass = Class.forName(className); Annotation[] annotations = getFieldAnnotations(entityClass, fieldName); String sqlTypeExtension = ""; if (annotations.length != 0) { for (Annotation annotation : annotations) { - // if (annotation.annotationType().equals(Constraint.class.getAnnotationsByType(Size.class))) { - // sqlTypeExtension = sqlTypeExtension + ""; - // } - // if (annotation.annotationType().isInstance(Column.class) && annotation.annotationType().get ) { - // Column column = annotation.annotationType().get - // - // } + if (sqlType == "VARCHAR" && annotation.annotationType().equals(Size.class)) { + Integer maxSize = ((Size) annotation).max(); // Size.max is always present as it defaults to + // Integer.MAX_VALUE; + sqlTypeExtension = sqlTypeExtension + "(" + maxSize.toString() + ")"; + } + if ((annotation.annotationType().equals(Column.class) && !((Column) annotation).nullable()) + || (annotation.annotationType().equals(NotNull.class))) { + sqlTypeExtension = sqlTypeExtension + " NOT NULL"; + } + if (annotation.annotationType().equals(GeneratedValue.class)) { + sqlTypeExtension = sqlTypeExtension + " AUTO_INCREMENT"; + } + } } return sqlType + sqlTypeExtension; From 0fc0c2a0df254661093e4760ee8827009ab4a089 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Tue, 30 Aug 2022 12:10:42 +0200 Subject: [PATCH 08/39] Functions for non collections --- .../templates/devon4j/utils/SQLUtil.java | 75 +++++++++++++++++-- ...ate_${variables.entityName}Entity.sql.ftl} | 0 2 files changed, 69 insertions(+), 6 deletions(-) rename cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/{${variables.entityName}Entity.sql.ftl => V0000__Create_${variables.entityName}Entity.sql.ftl} (100%) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 9f861ee31e..f8c0af3f2f 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -289,7 +289,72 @@ public String getPrimaryKey(String className) { } /** - * Method to get the SQL type based on annotations and field type + * Get the column name based on annotations or default to fieldName + * + * @param annotations Array of a field's annotations + * @param fieldName {@link String} the name of the field + * @return SQL Column name base or null + */ + public String getColumnName(Annotation[] annotations, String fieldName) { + + return ""; + } + + /** + * Get a primary key SQL statement based on the @ID annotation + * + * @param annotations Array of a field's annotations + * @param className {@link String} full qualified class name + * @param fieldName {@link String} the name of the field + * @return SQL Primary key statement or null + */ + public String getPrimaryKeyType(Annotation[] annotations, String className, String fieldName) { + + return ""; + } + + /** + * Get the Foreign Key and its type based on annotated options. If the field is annotated with JoinColumn the Foreign + * Key will be the type of the referenced column. + * + * @param annotations Array of a field's annotations + * @param className {@link String} full qualified class name + * @param fieldName {@link String} the name of the field + * @return SQL foreign key + "," + type or null + */ + public String getForeignKey(Annotation[] annotations, String className, String fieldName) { + + return "" + "," + ""; + } + + /** + * Get the Foreign Key name based on annotated options or default to "column_name + _id" + * + * @param annotations Array of a field's annotations + * @param className {@link String} full qualified class name + * @param fieldName {@link String} the name of the field + * @return SQL foreign key name or null + */ + public String getForeignKeyName(Annotation[] annotations, String className, String fieldName) { + + return ""; + } + + /** + * Get the Foreign table based on annotated options that will be referenced + * + * @param annotations Array of a field's annotations + * @param className {@link String} full qualified class name + * @param fieldName {@link String} the name of the field + * @return SQL foreign table to be referenced or null + */ + public String getForeignKeyTable(Annotation[] annotations, String className, String fieldName) { + + return "" + "," + ""; + } + + /** + * Method to get the SQL type statement for Primary Keys and simple Columns * * @param className {@link String} full qualified class name * @param fieldName {@link String} the name of the field @@ -297,17 +362,16 @@ public String getPrimaryKey(String className) { * @throws ClassNotFoundException */ @SuppressWarnings("javadoc") - public String getSqlType(String className, String fieldName) throws ClassNotFoundException { + public String getSqlTypeStatement(Annotation[] annotations, String className, String fieldName) + throws ClassNotFoundException { try { String sqlType = mapJavaToSqlType(getCanonicalNameOfFieldType(className, fieldName)); - Class entityClass = Class.forName(className); - Annotation[] annotations = getFieldAnnotations(entityClass, fieldName); String sqlTypeExtension = ""; if (annotations.length != 0) { for (Annotation annotation : annotations) { - if (sqlType == "VARCHAR" && annotation.annotationType().equals(Size.class)) { + if (sqlType.equals("VARCHAR") && annotation.annotationType().equals(Size.class)) { Integer maxSize = ((Size) annotation).max(); // Size.max is always present as it defaults to // Integer.MAX_VALUE; sqlTypeExtension = sqlTypeExtension + "(" + maxSize.toString() + ")"; @@ -319,7 +383,6 @@ public String getSqlType(String className, String fieldName) throws ClassNotFoun if (annotation.annotationType().equals(GeneratedValue.class)) { sqlTypeExtension = sqlTypeExtension + " AUTO_INCREMENT"; } - } } return sqlType + sqlTypeExtension; diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl similarity index 100% rename from cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/${variables.entityName}Entity.sql.ftl rename to cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl From 13df6ad4c888a0d02676ffbe7d8a5529590f8fd6 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Wed, 21 Sep 2022 18:57:01 +0200 Subject: [PATCH 09/39] Implemented Foreign Key Functions and template --- .../templates/devon4j/utils/SQLUtil.java | 126 +++++++++++++----- ...eate_${variables.entityName}Entity.sql.ftl | 125 ++++++++++++++--- 2 files changed, 193 insertions(+), 58 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index f8c0af3f2f..59f7bd7534 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -5,12 +5,16 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Optional; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; import javax.persistence.Table; import org.apache.commons.lang3.StringUtils; @@ -37,8 +41,9 @@ public SQLUtil() { * * @param canonicalTypeName {@link String} full qualified class name * @return returns the equivalent SQL type + * @throws IllegalArgumentException when type is not a java type */ - private String mapJavaToSqlType(String canonicalTypeName) throws IllegalArgumentException { + public String mapJavaToSqlType(String canonicalTypeName) throws IllegalArgumentException { try { @@ -289,68 +294,117 @@ public String getPrimaryKey(String className) { } /** - * Get the column name based on annotations or default to fieldName + * Helper method to build a hash map for foreign key values * - * @param annotations Array of a field's annotations - * @param fieldName {@link String} the name of the field - * @return SQL Column name base or null + * @param name name of the foreign key + * @param table the current table name + * @param columnname referenced column name + * @return */ - public String getColumnName(Annotation[] annotations, String fieldName) { + private HashMap fkMapBuild(String name, String table, String columnname) { - return ""; + HashMap foreignKeyMap = new HashMap() { + { + put("key", name); + put("table", table); + put("id", columnname); + } + }; + return foreignKeyMap; } /** - * Get a primary key SQL statement based on the @ID annotation + * Get a List of HashMaps holding the information for foreign keys assuming the current field is an entity * - * @param annotations Array of a field's annotations - * @param className {@link String} full qualified class name - * @param fieldName {@link String} the name of the field - * @return SQL Primary key statement or null + * @param field the pojo field + * @return List of Hash Map holding the information {"key": name, "table": table, "id": id} + * + * @key {@link String} the name of the foreign key + * @table {@link String} the table which is referenced by the foreign key + * @id {@link String} the name of the referenced with @id annotated variable */ - public String getPrimaryKeyType(Annotation[] annotations, String className, String fieldName) { + public List> getForeignKeyData(Field field) { + + String table = "", tableReceived; + List> foreignKeyData = new ArrayList<>(); + + // Assumes @JoinColumn is present + if (field.isAnnotationPresent(JoinColumn.class)) { + String[] fkDeclaration = getForeignKeyDeclaration(field).split(","); + String name = getForeignKeyName(field, fkDeclaration[1]); + String tableName = getForeignKeyTableName(field); + foreignKeyData.add(fkMapBuild(name, table, fkDeclaration[0])); + } - return ""; + return foreignKeyData; } /** - * Get the Foreign Key and its type based on annotated options. If the field is annotated with JoinColumn the Foreign - * Key will be the type of the referenced column. + * Get the table name of the current pojo * - * @param annotations Array of a field's annotations - * @param className {@link String} full qualified class name - * @param fieldName {@link String} the name of the field - * @return SQL foreign key + "," + type or null + * @param field field the pojo field + * @return {@link String} */ - public String getForeignKey(Annotation[] annotations, String className, String fieldName) { + public String getForeignKeyTableName(Field field) { - return "" + "," + ""; + try { + String tableName = ""; + if (field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToOne.class)) { + tableName = getEntityTableName(field.getType().getCanonicalName()); + } else { + tableName = field.getType().getCanonicalName(); + } + + return tableName; + } catch (ClassNotFoundException e) { + LOG.error("{}: Could not find {}", e.getMessage(), field.getType().getCanonicalName()); + } + return null; } /** - * Get the Foreign Key name based on annotated options or default to "column_name + _id" + * Retrieve the name of a referenced column and its type based on the provided field * - * @param annotations Array of a field's annotations - * @param className {@link String} full qualified class name - * @param fieldName {@link String} the name of the field - * @return SQL foreign key name or null + * @param field current pojo class + * @return comma separated String or null */ - public String getForeignKeyName(Annotation[] annotations, String className, String fieldName) { + public String getForeignKeyDeclaration(Field field) { + + String columnName, type; + try { + if (field.isAnnotationPresent(JoinColumn.class) + && !field.getAnnotation(JoinColumn.class).referencedColumnName().isEmpty()) { + columnName = field.getAnnotation(JoinColumn.class).referencedColumnName(); + type = mapJavaToSqlType(getCanonicalNameOfFieldType(field.getType().getCanonicalName(), columnName)); + } else { + String[] pkReceived = getPrimaryKey(field.getType().getCanonicalName()).split(","); + type = mapJavaToSqlType(pkReceived[0]); + columnName = pkReceived[1]; + } + return columnName + "," + type; - return ""; + } catch (ClassNotFoundException e) { + LOG.error("{}: Could not find {}", e.getMessage(), field.getType().getCanonicalName()); + } + return null; } /** - * Get the Foreign table based on annotated options that will be referenced + * Get the name of a @JoinColumn or fall back to a default in name in case the name option is not set * - * @param annotations Array of a field's annotations - * @param className {@link String} full qualified class name - * @param fieldName {@link String} the name of the field - * @return SQL foreign table to be referenced or null + * @param field current pojo class + * @param fallBack String for fallback option + * @return {@link String} Column name */ - public String getForeignKeyTable(Annotation[] annotations, String className, String fieldName) { + public String getForeignKeyName(Field field, String fallBack) { - return "" + "," + ""; + String name; + if (field.isAnnotationPresent(JoinColumn.class) && !field.getAnnotation(JoinColumn.class).name().isBlank()) { + name = field.getAnnotation(JoinColumn.class).name(); + } else { + name = field.getName() + "_" + fallBack; + } + return name; } /** diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl index eb8f77623d..cd8ca4bc40 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -1,23 +1,104 @@ -CREATE TABLE ${variables.entityName?upper_case} ( - ID DECIMAL(10,0), - VERSION INTEGER NOT NULL, -<#list pojo.fields as field> -<#if !field.type?starts_with("List<") && !field.type?starts_with("Set<")> - <#if field.type?contains("Entity") || field.type=='long' || field.type=='java.lang.Long'> - <#assign type = 'DECIMAL(10,0)'> - <#elseif field.type=='int' || field.type=='java.lang.Integer' || field.type=='java.time.Year' || field.type=='java.time.Month'> - <#assign type = 'INTEGER'> - <#elseif field.type=='java.time.Instant' || field.type=='java.sql.Timestamp'> - <#assign type = 'TIMESTAMP'> - <#elseif field.type=='java.util.Date'> - <#assign type = 'DATE'> - <#elseif field.type=='boolean'> - <#assign type = 'NUMBER(1) DEFAULT 0'> - <#else> - <#assign type = 'VARCHAR2(255 CHAR)'> - - ${field.name?upper_case?right_pad(30)}${type}, - +<#assign tableName = JavaUtil.getEntityTableName(pojo.canonicalName)> +CREATE TABLE ${tableName} ( +<#assign fkList = []> +<#assign columns = []> +<#assign refTables = []> +<#list pojo.methodAccessibleFields as field> + <#if !field.annotations.javax_persistence_Transient??> + <#if field.annotations.javax_persistence_Column?? && field.annotations.javax_persistence_Column.name?has_content> + <#assign name = field.annotations.javax_persistence_Column.name> + <#else> + <#assign name = field.name> + + <#--Field: primary key--> + <#if field.annotations.javax_persistence_Id??> + <#assign pk = name> + <#assign type = get_type(field)> + <#if !type?contains("NOT NULL")> + <#assign type = type + " NOT NULL"> + + <#if field.annotations.javax_persistence_GeneratedValue?? + && field.annotations.javax_persistence_GeneratedValue.strategy??> + <#assign type = type + " AUTO_INCREMENT"> + + <#assign columns = columns + [{"name": name, "type":type}]> + <#elseif !JavaUtil.isCollection2(classObject, field.name)> + <#--Field: simple entity--> + <#if field.type?ends_with("Entity")> + <#assign type = SQLUtil.getForeignKeyDeclaration(field)?split(",")> + <#assign fkList = fkList + [SQLUtil.getForeignKeyData(field)]> + <#else> + <#--Field: primitive--> + <#assign type = get_type(field)/> + + <#assign columns = columns + [{"name": name, "type":type}]> + <#else> + <#if field.annotations.javax_persistence_ManyToMany?? && field.annotations.javax_persistence_JoinTable??> + <#--Field: collection of entity--> + <#assign entity = field.canonicalType?substring(field.canonicalType?index_of("<") + 1,field.canonicalType?length - 1)> + <#assign entityTable = JavaUtil.getEntityTableName(entity)> + <#assign table1 = tableName> + <#assign table2 = entityTable> + <#if field.annotations.javax_persistence_JoinTable.name?has_content> + <#assign refTableName = field.annotations.javax_persistence_JoinTable.name> + <#else> + <#assign refTableName = table1 + "_" + table2> + + <#--not yet support multiple JoinColumns or no JoinColumn--> + <#if field.annotations.javax_persistence_JoinTable.joinColumns?has_content + && field.annotations.javax_persistence_JoinTable.joinColumns?is_enumerable + && field.annotations.javax_persistence_JoinTable.joinColumns[0]?has_content> + <#assign col = field.annotations.javax_persistence_JoinTable.joinColumns[0].javax_persistence_JoinColumn> + <#assign name1 = col.name> + <#if col.referencedColumnName?has_content> + <#assign id1 = col.referencedColumnName> + <#assign type1 = get_mapping_type(JavaUtil.getCanonicalNameOfField(pojo.canonicalName, id1))> + <#else> + <#assign result = JavaUtil.getPrimaryKey(pojo.canonicalName)?split(",")> + <#assign type1 = get_mapping_type(result[0])> + <#assign id1 = result[1]> + + <#else> + <#continue> + + <#if field.annotations.javax_persistence_JoinTable.inverseJoinColumns?has_content + && field.annotations.javax_persistence_JoinTable.inverseJoinColumns?is_enumerable + && field.annotations.javax_persistence_JoinTable.inverseJoinColumns[0]?has_content> + <#assign col = field.annotations.javax_persistence_JoinTable.inverseJoinColumns[0].javax_persistence_JoinColumn> + <#assign name2 = col.name> + <#if col.referencedColumnName?has_content> + <#assign id2 = col.referencedColumnName> + <#assign type2 = get_mapping_type(JavaUtil.getCanonicalNameOfField(entity, id2))> + <#else> + <#assign result = JavaUtil.getPrimaryKey(entity)?split(",")> + <#assign type2 = get_mapping_type(result[0])> + <#assign id2 = result[1]> + + <#else> + <#continue> + + <#assign refTables = refTables + [{"table": refTableName, "columns":[{"name": name1, "id": id1, "type": type1, "table": table1}, {"name": name2, "id": id2, "type": type2, "table": table2}] }]> + + + - CONSTRAINT PK_${variables.entityName?upper_case} PRIMARY KEY(ID) -) \ No newline at end of file +<#list columns as col> + ${col.name?right_pad(30)} ${col.type}, + + CONSTRAINT PK_${tableName} PRIMARY KEY(${pk}), +<#list fkList as fk> + CONSTRAINT FK_${tableName}_${fk.key} FOREIGN KEY(${fk.key}) REFERENCES ${fk.table}(${fk.id}), + +); +<#list refTables as tb> + +CREATE TABLE ${tb.table} ( +<#assign col1 = tb.columns[0]> +<#assign col2 = tb.columns[1]> + ${col1.name?right_pad(30)} ${col1.type} NOT NULL, + ${col2.name?right_pad(30)} ${col2.type} NOT NULL, + CONSTRAINT PK_${tb.table} PRIMARY KEY(${col1.name}, ${col2.name}), + CONSTRAINT FK_${tb.table}_${col1.name} FOREIGN KEY(${col1.name}) REFERENCES ${col1.table}(${col1.id}), + CONSTRAINT FK_${tb.table}_${col2.name} FOREIGN KEY(${col2.name}) REFERENCES ${col2.table}(${col2.id}), +); + \ No newline at end of file From 9c3cc26c77ddbe9d66a17a5ed7575b85cbba81ca Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 23 Sep 2022 17:23:32 +0200 Subject: [PATCH 10/39] Improved javadoc, implemented mappedby aspect into foreignkey method, removed unnecessary List wrapper around HashMaps --- .../templates/devon4j/utils/SQLUtil.java | 97 ++++++++++++++----- ...eate_${variables.entityName}Entity.sql.ftl | 4 +- 2 files changed, 73 insertions(+), 28 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 59f7bd7534..cc16196e68 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -213,9 +213,8 @@ public Annotation[] getFieldAnnotations(Class pojoClass, String fieldName) { * * @param className {@link String} full qualified class name * @return return the annotated table name if - * @throws ClassNotFoundException + * @throws ClassNotFoundException to Log an error */ - @SuppressWarnings("javadoc") public String getEntityTableName(String className) throws ClassNotFoundException { if (!className.endsWith("Entity")) { @@ -239,9 +238,8 @@ public String getEntityTableName(String className) throws ClassNotFoundException * @param className {@link String} full qualified class name * @param fieldName {@link String} the name of the field * @return type of the field in the string - * @throws ClassNotFoundException + * @throws ClassNotFoundException to Log an error */ - @SuppressWarnings("javadoc") public String getCanonicalNameOfFieldType(String className, String fieldName) throws ClassNotFoundException { try { @@ -294,61 +292,80 @@ public String getPrimaryKey(String className) { } /** - * Helper method to build a hash map for foreign key values + * Helper method to build a hash map for foreign key value pairs * * @param name name of the foreign key - * @param table the current table name + * @param table table the current table name * @param columnname referenced column name - * @return + * @return HashMap for name, table and column name */ private HashMap fkMapBuild(String name, String table, String columnname) { HashMap foreignKeyMap = new HashMap() { { - put("key", name); + put("name", name); put("table", table); - put("id", columnname); + put("columnname", columnname); } }; return foreignKeyMap; } /** - * Get a List of HashMaps holding the information for foreign keys assuming the current field is an entity + * Helper method to build a hash map for foreign key value pairs + * + * @param name name of the column + * @param type type of the column + * @return HashMap containing name and type of a column + */ + private HashMap columnMapBuild(String name, String type) { + + HashMap columnMap = new HashMap() { + { + put("name", name); + put("type", type); + } + }; + + return columnMap; + } + + /** + * Get a List of Key and Value pairs holding the information about foreign keys. It will be assumed that the current + * field is an entity. * * @param field the pojo field - * @return List of Hash Map holding the information {"key": name, "table": table, "id": id} + * @return HashMap holding the information {"name": name, "table": table, "columnname": columnname} or null * - * @key {@link String} the name of the foreign key + * @name {@link String} the name of the foreign key * @table {@link String} the table which is referenced by the foreign key - * @id {@link String} the name of the referenced with @id annotated variable + * @columnname {@link String} the column name of the referenced with @id annotated variable */ - public List> getForeignKeyData(Field field) { - - String table = "", tableReceived; - List> foreignKeyData = new ArrayList<>(); + public HashMap getForeignKeyData(Field field) { // Assumes @JoinColumn is present if (field.isAnnotationPresent(JoinColumn.class)) { String[] fkDeclaration = getForeignKeyDeclaration(field).split(","); String name = getForeignKeyName(field, fkDeclaration[1]); String tableName = getForeignKeyTableName(field); - foreignKeyData.add(fkMapBuild(name, table, fkDeclaration[0])); + HashMap foreignKeyData = fkMapBuild(name, tableName, fkDeclaration[0]); + + return foreignKeyData; } - return foreignKeyData; + return null; } /** - * Get the table name of the current pojo + * Get the table for the foreign key that the current field was annotated with * - * @param field field the pojo field - * @return {@link String} + * @param field current pojo class + * @return table name as a {@link String} or null */ public String getForeignKeyTableName(Field field) { try { - String tableName = ""; + String tableName; if (field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToOne.class)) { tableName = getEntityTableName(field.getType().getCanonicalName()); } else { @@ -363,7 +380,10 @@ public String getForeignKeyTableName(Field field) { } /** - * Retrieve the name of a referenced column and its type based on the provided field + * Retrieve the name of a referenced column and its type based on the provided field. @JoinColumn has precedence + * over @OneToMany when the field is annotated with both the option referencedColumnName from the @JoinColumn + * annotation and the mappedBy option from the @OneToMany annotation. The method will find the owner of the entity if + * none of the options are provided. * * @param field current pojo class * @return comma separated String or null @@ -376,7 +396,17 @@ public String getForeignKeyDeclaration(Field field) { && !field.getAnnotation(JoinColumn.class).referencedColumnName().isEmpty()) { columnName = field.getAnnotation(JoinColumn.class).referencedColumnName(); type = mapJavaToSqlType(getCanonicalNameOfFieldType(field.getType().getCanonicalName(), columnName)); + } else if (field.isAnnotationPresent(OneToOne.class) + && !field.getAnnotation(OneToOne.class).mappedBy().isBlank()) { + // If the field is annotated with the mappedBy option from the @OneToOne annotation + // we have to get the name of that class + Class entityClass = Class + .forName(field.getAnnotation(OneToOne.class).mappedBy().getClass().getCanonicalName()); + columnName = entityClass.getCanonicalName(); + type = entityClass.getTypeName(); } else { + // If the field wasn't annotated with either a @JoinColumn or any relationship annotation we have to find the + // owner of the current field String[] pkReceived = getPrimaryKey(field.getType().getCanonicalName()).split(","); type = mapJavaToSqlType(pkReceived[0]); columnName = pkReceived[1]; @@ -407,15 +437,30 @@ public String getForeignKeyName(Field field, String fallBack) { return name; } + /** + * Get a List of HashMap Keys holding information about a given field + * + * @param field the current pojo field + * @param name the column name related to that field + * @return HashMap containing name and type of a column + */ + // public HashMap getPrimaryKeySQLstatement(Field field, String name) { + // + // List> sqlStatement = new ArrayList>(); + // + // HashMap column = columnMapBuild(); + // + // return column; + // } + /** * Method to get the SQL type statement for Primary Keys and simple Columns * * @param className {@link String} full qualified class name * @param fieldName {@link String} the name of the field * @return SQL type as a String - * @throws ClassNotFoundException + * @throws ClassNotFoundException to Log an error */ - @SuppressWarnings("javadoc") public String getSqlTypeStatement(Annotation[] annotations, String className, String fieldName) throws ClassNotFoundException { diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl index cd8ca4bc40..b1f5325eb6 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -25,11 +25,11 @@ CREATE TABLE ${tableName} ( <#elseif !JavaUtil.isCollection2(classObject, field.name)> <#--Field: simple entity--> <#if field.type?ends_with("Entity")> - <#assign type = SQLUtil.getForeignKeyDeclaration(field)?split(",")> + <#assign type = SQLUtil.getForeignKeyDeclaration(field)?split(",")[1]> <#assign fkList = fkList + [SQLUtil.getForeignKeyData(field)]> <#else> <#--Field: primitive--> - <#assign type = get_type(field)/> + <#assign type = get_type(field)> <#assign columns = columns + [{"name": name, "type":type}]> <#else> From d6678f428d595ce83b0989d32a3231a06ddc80bb Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 23 Sep 2022 18:36:15 +0200 Subject: [PATCH 11/39] Implemented getSImpleSQLtype and Primary key methods --- .../templates/devon4j/utils/SQLUtil.java | 105 +++++++++++------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index cc16196e68..c44eb51bdf 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -11,6 +11,7 @@ import javax.persistence.Column; import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @@ -132,6 +133,30 @@ public String mapJavaToSqlType(String canonicalTypeName) throws IllegalArgumentE } + /** + * Get a basic SQL type statement for primitives. + * + * @param field field of the pojo class + * @return SQL statement as {@link String} + */ + public String getSimpleSQLtype(Field field) { + + String sqlStatement = ""; + String type = mapJavaToSqlType(field.getType().getCanonicalName()); + + if (type.contains("VARCHAR") && field.isAnnotationPresent(Size.class)) { + Integer maxSize = field.getAnnotation(Size.class).max(); + sqlStatement = sqlStatement + "(" + maxSize.toString() + ")"; + } + + if (field.isAnnotationPresent(Column.class) && !field.getAnnotation(Column.class).nullable() + || field.isAnnotationPresent(NotNull.class)) { + sqlStatement = sqlStatement + " NOT NULL"; + } + + return sqlStatement; + } + /** * Helper methods to get all fields recursively including fields from super classes * @@ -438,57 +463,55 @@ public String getForeignKeyName(Field field, String fallBack) { } /** - * Get a List of HashMap Keys holding information about a given field + * Returns the name of a field. When the @Column annotation sets the name option that name will be used. + * + * @param field the current pojo field + * @return {@link String} + */ + public String getColumnName(Field field) { + + return field.isAnnotationPresent(Column.class) && !field.getAnnotation(Column.class).name().isBlank() + ? field.getAnnotation(Column.class).name() + : field.getName(); + } + + /** + * Get a List of HashMap Keys holding information about a given field. Default value overloaded by + * {@link SQLUtil#getPrimaryKeySQLstatement(Field, String)} * * @param field the current pojo field - * @param name the column name related to that field * @return HashMap containing name and type of a column */ - // public HashMap getPrimaryKeySQLstatement(Field field, String name) { - // - // List> sqlStatement = new ArrayList>(); - // - // HashMap column = columnMapBuild(); - // - // return column; - // } + public HashMap getPrimaryKeySQLstatement(Field field) { + + String name = getColumnName(field); + HashMap column = getPrimaryKeySQLstatement(field, name); + return column; + } /** - * Method to get the SQL type statement for Primary Keys and simple Columns + * Get a List of HashMap Keys holding information about a given field * - * @param className {@link String} full qualified class name - * @param fieldName {@link String} the name of the field - * @return SQL type as a String - * @throws ClassNotFoundException to Log an error + * @param field the current pojo field + * @param name the column name related to that field + * @return HashMap containing name and type of a column */ - public String getSqlTypeStatement(Annotation[] annotations, String className, String fieldName) - throws ClassNotFoundException { + public HashMap getPrimaryKeySQLstatement(Field field, String name) { - try { - String sqlType = mapJavaToSqlType(getCanonicalNameOfFieldType(className, fieldName)); - String sqlTypeExtension = ""; - - if (annotations.length != 0) { - for (Annotation annotation : annotations) { - if (sqlType.equals("VARCHAR") && annotation.annotationType().equals(Size.class)) { - Integer maxSize = ((Size) annotation).max(); // Size.max is always present as it defaults to - // Integer.MAX_VALUE; - sqlTypeExtension = sqlTypeExtension + "(" + maxSize.toString() + ")"; - } - if ((annotation.annotationType().equals(Column.class) && !((Column) annotation).nullable()) - || (annotation.annotationType().equals(NotNull.class))) { - sqlTypeExtension = sqlTypeExtension + " NOT NULL"; - } - if (annotation.annotationType().equals(GeneratedValue.class)) { - sqlTypeExtension = sqlTypeExtension + " AUTO_INCREMENT"; - } - } - } - return sqlType + sqlTypeExtension; - } catch (ClassNotFoundException e) { - LOG.error("{}: Could not find {}", e.getMessage(), className); - return null; + String type = getSimpleSQLtype(field); + + if (!type.contains("NOT NULL")) { + // Make sure Primary Keys will always be created with the NOT NULL statement + type = type + " NOT NULL"; } + + if (field.isAnnotationPresent(GeneratedValue.class) + && field.getAnnotation(GeneratedValue.class).strategy().equals(GenerationType.AUTO)) { + type = type + " AUTO INCREMENT"; + } + HashMap column = columnMapBuild(name, type); + + return column; } } From d4d30276aa0112549461a2e5f10a95f359e7d76d Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Mon, 26 Sep 2022 12:25:23 +0200 Subject: [PATCH 12/39] Added a default parameter to getForeignKeyData and fixed bug in getForeignKeyStatement --- .mvn/maven.config | 1 + .../templates/devon4j/utils/SQLUtil.java | 66 +++++-- ...eate_${variables.entityName}Entity.sql.ftl | 18 +- .../src/main/resources/logback.xml | 15 ++ sonar-report/pom.xml | 164 ++++++++++++++++++ 5 files changed, 235 insertions(+), 29 deletions(-) create mode 100644 .mvn/maven.config create mode 100644 cobigen/cobigen-core-test/src/main/resources/logback.xml create mode 100644 sonar-report/pom.xml diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 0000000000..8f1c8bb1f8 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1 @@ +-Dsonar.projectKey=devonfw_cobigen \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index c44eb51bdf..5b8c42d7fa 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -357,7 +357,8 @@ private HashMap columnMapBuild(String name, String type) { /** * Get a List of Key and Value pairs holding the information about foreign keys. It will be assumed that the current - * field is an entity. + * field is an entity. This function defaults the {@link String} name parameter of + * {@link SQLUtil#getForeignKeyData(Field, String)} * * @param field the pojo field * @return HashMap holding the information {"name": name, "table": table, "columnname": columnname} or null @@ -370,8 +371,34 @@ public HashMap getForeignKeyData(Field field) { // Assumes @JoinColumn is present if (field.isAnnotationPresent(JoinColumn.class)) { - String[] fkDeclaration = getForeignKeyDeclaration(field).split(","); - String name = getForeignKeyName(field, fkDeclaration[1]); + String[] fkDeclaration = getForeignKeyStatement(field).split(","); + String name = getForeignKeyName(field, fkDeclaration[0]); + String tableName = getForeignKeyTableName(field); + HashMap foreignKeyData = fkMapBuild(name, tableName, fkDeclaration[0]); + + return foreignKeyData; + } + + return null; + } + + /** + * Get a List of Key and Value pairs holding the information about foreign keys. It will be assumed that the current + * field is an entity. + * + * @param field the pojo field + * @param name the name of the foreign key + * @return HashMap holding the information {"name": name, "table": table, "columnname": columnname} or null + * + * @name {@link String} the name of the foreign key + * @table {@link String} the table which is referenced by the foreign key + * @columnname {@link String} the column name of the referenced with @id annotated variable + */ + public HashMap getForeignKeyData(Field field, String name) { + + // Assumes @JoinColumn is present + if (field.isAnnotationPresent(JoinColumn.class)) { + String[] fkDeclaration = getForeignKeyStatement(field).split(","); String tableName = getForeignKeyTableName(field); HashMap foreignKeyData = fkMapBuild(name, tableName, fkDeclaration[0]); @@ -405,37 +432,47 @@ public String getForeignKeyTableName(Field field) { } /** - * Retrieve the name of a referenced column and its type based on the provided field. @JoinColumn has precedence - * over @OneToMany when the field is annotated with both the option referencedColumnName from the @JoinColumn - * annotation and the mappedBy option from the @OneToMany annotation. The method will find the owner of the entity if - * none of the options are provided. + * Retrieve the name of a referenced column and its type statement based on the provided field. @JoinColumn has + * precedence over @OneToMany when the field is annotated with both the option referencedColumnName from + * the @JoinColumn annotation and the mappedBy option from the @OneToMany annotation. The method will find the owner + * of the entity if none of the options are provided. * * @param field current pojo class * @return comma separated String or null */ - public String getForeignKeyDeclaration(Field field) { + public String getForeignKeyStatement(Field field) { - String columnName, type; + String columnName = "", type = ""; + Class foreignEntityClass; try { if (field.isAnnotationPresent(JoinColumn.class) && !field.getAnnotation(JoinColumn.class).referencedColumnName().isEmpty()) { + // Annotation @JoinColumn is set and a name was provided columnName = field.getAnnotation(JoinColumn.class).referencedColumnName(); - type = mapJavaToSqlType(getCanonicalNameOfFieldType(field.getType().getCanonicalName(), columnName)); + foreignEntityClass = Class.forName(field.getAnnotation(JoinColumn.class).referencedColumnName()); + } else if (field.isAnnotationPresent(OneToOne.class) && !field.getAnnotation(OneToOne.class).mappedBy().isBlank()) { // If the field is annotated with the mappedBy option from the @OneToOne annotation // we have to get the name of that class - Class entityClass = Class + foreignEntityClass = Class .forName(field.getAnnotation(OneToOne.class).mappedBy().getClass().getCanonicalName()); - columnName = entityClass.getCanonicalName(); - type = entityClass.getTypeName(); + } else { // If the field wasn't annotated with either a @JoinColumn or any relationship annotation we have to find the // owner of the current field String[] pkReceived = getPrimaryKey(field.getType().getCanonicalName()).split(","); - type = mapJavaToSqlType(pkReceived[0]); + foreignEntityClass = Class.forName(field.getType().getCanonicalName()); columnName = pkReceived[1]; } + + try { + Field foreignField = foreignEntityClass.getDeclaredField(columnName); + type = getSimpleSQLtype(foreignField); + } catch (NoSuchFieldException e) { + LOG.error("{}: Could not find the field", e.getMessage()); + } + return columnName + "," + type; } catch (ClassNotFoundException e) { @@ -509,6 +546,7 @@ public HashMap getPrimaryKeySQLstatement(Field field, String nam && field.getAnnotation(GeneratedValue.class).strategy().equals(GenerationType.AUTO)) { type = type + " AUTO INCREMENT"; } + HashMap column = columnMapBuild(name, type); return column; diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl index b1f5325eb6..62b9c83733 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -5,24 +5,12 @@ CREATE TABLE ${tableName} ( <#assign refTables = []> <#list pojo.methodAccessibleFields as field> <#if !field.annotations.javax_persistence_Transient??> - <#if field.annotations.javax_persistence_Column?? && field.annotations.javax_persistence_Column.name?has_content> - <#assign name = field.annotations.javax_persistence_Column.name> - <#else> - <#assign name = field.name> - + <#assign name = SQLUtil.getColumnName(field)> <#--Field: primary key--> <#if field.annotations.javax_persistence_Id??> <#assign pk = name> - <#assign type = get_type(field)> - <#if !type?contains("NOT NULL")> - <#assign type = type + " NOT NULL"> - - <#if field.annotations.javax_persistence_GeneratedValue?? - && field.annotations.javax_persistence_GeneratedValue.strategy??> - <#assign type = type + " AUTO_INCREMENT"> - - <#assign columns = columns + [{"name": name, "type":type}]> - <#elseif !JavaUtil.isCollection2(classObject, field.name)> + <#assign columns = columns + [SQLUtil.getPrimaryKeySQLstatement(field, name)]> + <#elseif !SQLUtil.isCollection(classObject, field.name)> <#--Field: simple entity--> <#if field.type?ends_with("Entity")> <#assign type = SQLUtil.getForeignKeyDeclaration(field)?split(",")[1]> diff --git a/cobigen/cobigen-core-test/src/main/resources/logback.xml b/cobigen/cobigen-core-test/src/main/resources/logback.xml new file mode 100644 index 0000000000..5c3e0aa3f4 --- /dev/null +++ b/cobigen/cobigen-core-test/src/main/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/sonar-report/pom.xml b/sonar-report/pom.xml new file mode 100644 index 0000000000..b68081935b --- /dev/null +++ b/sonar-report/pom.xml @@ -0,0 +1,164 @@ + + + 4.0.0 + + + com.devonfw.cobigen + master-parent + ${revision} + + + + sonar-report + Aggregated Sonar Report + + + 11 + + + + + + ${project.groupId} + core + ${revision} + + + ${project.groupId} + core-api + ${revision} + + + ${project.groupId} + core-systemtest + ${revision} + test + + + ${project.groupId} + core-externalprocess-api + ${revision} + + + ${project.groupId} + htmlplugin + ${revision} + + + ${project.groupId} + javaplugin + ${revision} + + + ${project.groupId} + javaplugin-model + ${revision} + + + ${project.groupId} + jsonplugin + ${revision} + + + ${project.groupId} + openapiplugin + ${revision} + + + ${project.groupId} + openapiplugin-model + ${revision} + + + ${project.groupId} + propertyplugin + ${revision} + + + ${project.groupId} + tempeng-velocity + ${revision} + + + ${project.groupId} + tempeng-freemarker + ${revision} + + + ${project.groupId} + textmerger + ${revision} + + + ${project.groupId} + tsplugin + ${revision} + + + ${project.groupId} + xmlplugin + ${revision} + + + ${project.groupId} + cli + ${revision} + + + ${project.groupId} + cli-systemtest + ${revision} + test + + + ${project.groupId} + com.devonfw.cobigen.eclipse + ${revision} + + + ${project.groupId} + com.devonfw.cobigen.eclipse.test + ${revision} + test + + + ${project.groupId} + cobigen-maven-plugin + ${revision} + + + ${project.groupId} + maven-systemtest + ${revision} + test + + + ${project.groupId} + maven-test + ${revision} + test + + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + report-aggregate + verify + + report-aggregate + + + + + + + + \ No newline at end of file From 5b05bd6c7e0494326a42b181ef61adde359e0854 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Mon, 26 Sep 2022 16:50:01 +0200 Subject: [PATCH 13/39] getForeignKeyStatement and template - added Test class for SQL Util --- .../templates/devon4j/utils/SQLUtil.java | 165 +++++++++--------- ...eate_${variables.entityName}Entity.sql.ftl | 9 +- .../devon4j/test/utils/SQLUtilTest.java | 17 ++ 3 files changed, 105 insertions(+), 86 deletions(-) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 5b8c42d7fa..cc76649efa 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -317,23 +317,17 @@ public String getPrimaryKey(String className) { } /** - * Helper method to build a hash map for foreign key value pairs + * Get a List of HashMap Keys holding information about a given field. Default value overloaded by + * {@link SQLUtil#getPrimaryKeyStatement(Field, String)} * - * @param name name of the foreign key - * @param table table the current table name - * @param columnname referenced column name - * @return HashMap for name, table and column name + * @param field the current pojo field + * @return HashMap containing name and type of a column */ - private HashMap fkMapBuild(String name, String table, String columnname) { + public HashMap getPrimaryKeyStatement(Field field) { - HashMap foreignKeyMap = new HashMap() { - { - put("name", name); - put("table", table); - put("columnname", columnname); - } - }; - return foreignKeyMap; + String name = getColumnName(field); + HashMap column = getPrimaryKeyStatement(field, name); + return column; } /** @@ -355,6 +349,65 @@ private HashMap columnMapBuild(String name, String type) { return columnMap; } + /** + * Get a List of HashMap Keys holding information about a given field + * + * @param field the current pojo field + * @param name the column name related to that field + * @return HashMap containing name and type of a column + */ + public HashMap getPrimaryKeyStatement(Field field, String name) { + + String type = getSimpleSQLtype(field); + + if (!type.contains("NOT NULL")) { + // Make sure Primary Keys will always be created with the NOT NULL statement + type = type + " NOT NULL"; + } + + if (field.isAnnotationPresent(GeneratedValue.class) + && field.getAnnotation(GeneratedValue.class).strategy().equals(GenerationType.AUTO)) { + type = type + " AUTO INCREMENT"; + } + + HashMap column = columnMapBuild(name, type); + + return column; + } + + /** + * Returns the name of a field. When the @Column annotation sets the name option that name will be used. + * + * @param field the current pojo field + * @return {@link String} + */ + public String getColumnName(Field field) { + + return field.isAnnotationPresent(Column.class) && !field.getAnnotation(Column.class).name().isBlank() + ? field.getAnnotation(Column.class).name() + : field.getName(); + } + + /** + * Helper method to build a hash map for foreign key value pairs + * + * @param name name of the foreign key + * @param table table the current table name + * @param columnname referenced column name + * @return HashMap for name, table and column name + */ + private HashMap fkMapBuild(String name, String table, String columnname) { + + HashMap foreignKeyMap = new HashMap() { + { + put("name", name); + put("table", table); + put("columnname", columnname); + } + }; + return foreignKeyMap; + } + /** * Get a List of Key and Value pairs holding the information about foreign keys. It will be assumed that the current * field is an entity. This function defaults the {@link String} name parameter of @@ -373,10 +426,8 @@ public HashMap getForeignKeyData(Field field) { if (field.isAnnotationPresent(JoinColumn.class)) { String[] fkDeclaration = getForeignKeyStatement(field).split(","); String name = getForeignKeyName(field, fkDeclaration[0]); - String tableName = getForeignKeyTableName(field); - HashMap foreignKeyData = fkMapBuild(name, tableName, fkDeclaration[0]); - return foreignKeyData; + return getForeignKeyData(field, name); } return null; @@ -432,38 +483,41 @@ public String getForeignKeyTableName(Field field) { } /** - * Retrieve the name of a referenced column and its type statement based on the provided field. @JoinColumn has + * Retrieve the name of a referenced column name and its type statement based on the provided field. @JoinColumn has * precedence over @OneToMany when the field is annotated with both the option referencedColumnName from * the @JoinColumn annotation and the mappedBy option from the @OneToMany annotation. The method will find the owner - * of the entity if none of the options are provided. + * of the entity if none of the options are provided. This method will mostly be used on Entity types. * * @param field current pojo class * @return comma separated String or null */ public String getForeignKeyStatement(Field field) { - String columnName = "", type = ""; - Class foreignEntityClass; + String columnName, type = ""; + try { + // The current field type is the class in which we are searching the primary key + Class foreignEntityClass = Class.forName(field.getType().getCanonicalName()); + + // Building the column name based on provided annotations if (field.isAnnotationPresent(JoinColumn.class) && !field.getAnnotation(JoinColumn.class).referencedColumnName().isEmpty()) { // Annotation @JoinColumn is set and a name was provided columnName = field.getAnnotation(JoinColumn.class).referencedColumnName(); - foreignEntityClass = Class.forName(field.getAnnotation(JoinColumn.class).referencedColumnName()); } else if (field.isAnnotationPresent(OneToOne.class) && !field.getAnnotation(OneToOne.class).mappedBy().isBlank()) { - // If the field is annotated with the mappedBy option from the @OneToOne annotation - // we have to get the name of that class - foreignEntityClass = Class - .forName(field.getAnnotation(OneToOne.class).mappedBy().getClass().getCanonicalName()); + // mappedBy option is set by @OneToOne annotation + columnName = field.getAnnotation(OneToOne.class).mappedBy(); } else { - // If the field wasn't annotated with either a @JoinColumn or any relationship annotation we have to find the - // owner of the current field + // No information was provided and we are looking for the primary key manually String[] pkReceived = getPrimaryKey(field.getType().getCanonicalName()).split(","); - foreignEntityClass = Class.forName(field.getType().getCanonicalName()); + if (pkReceived.equals(null)) { + return null; + } columnName = pkReceived[1]; + } try { @@ -499,57 +553,4 @@ public String getForeignKeyName(Field field, String fallBack) { return name; } - /** - * Returns the name of a field. When the @Column annotation sets the name option that name will be used. - * - * @param field the current pojo field - * @return {@link String} - */ - public String getColumnName(Field field) { - - return field.isAnnotationPresent(Column.class) && !field.getAnnotation(Column.class).name().isBlank() - ? field.getAnnotation(Column.class).name() - : field.getName(); - } - - /** - * Get a List of HashMap Keys holding information about a given field. Default value overloaded by - * {@link SQLUtil#getPrimaryKeySQLstatement(Field, String)} - * - * @param field the current pojo field - * @return HashMap containing name and type of a column - */ - public HashMap getPrimaryKeySQLstatement(Field field) { - - String name = getColumnName(field); - HashMap column = getPrimaryKeySQLstatement(field, name); - return column; - } - - /** - * Get a List of HashMap Keys holding information about a given field - * - * @param field the current pojo field - * @param name the column name related to that field - * @return HashMap containing name and type of a column - */ - public HashMap getPrimaryKeySQLstatement(Field field, String name) { - - String type = getSimpleSQLtype(field); - - if (!type.contains("NOT NULL")) { - // Make sure Primary Keys will always be created with the NOT NULL statement - type = type + " NOT NULL"; - } - - if (field.isAnnotationPresent(GeneratedValue.class) - && field.getAnnotation(GeneratedValue.class).strategy().equals(GenerationType.AUTO)) { - type = type + " AUTO INCREMENT"; - } - - HashMap column = columnMapBuild(name, type); - - return column; - } - } diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl index 62b9c83733..4c6588413b 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -3,7 +3,7 @@ CREATE TABLE ${tableName} ( <#assign fkList = []> <#assign columns = []> <#assign refTables = []> -<#list pojo.methodAccessibleFields as field> +<#list pojo.methodAccessibleFields as field> <#if !field.annotations.javax_persistence_Transient??> <#assign name = SQLUtil.getColumnName(field)> <#--Field: primary key--> @@ -13,11 +13,12 @@ CREATE TABLE ${tableName} ( <#elseif !SQLUtil.isCollection(classObject, field.name)> <#--Field: simple entity--> <#if field.type?ends_with("Entity")> - <#assign type = SQLUtil.getForeignKeyDeclaration(field)?split(",")[1]> - <#assign fkList = fkList + [SQLUtil.getForeignKeyData(field)]> + <#assign name = SQLUtil.getForeignKeyName(field)> + <#assign type = SQLUtil.getForeignKeyDeclaration(field, name)?split(",")[1]> + <#assign fkList = fkList + [SQLUtil.getForeignKeyData(field, name)]> <#else> <#--Field: primitive--> - <#assign type = get_type(field)> + <#assign type = SQLUtil.getSimpleSQLtype(field)> <#assign columns = columns + [{"name": name, "type":type}]> <#else> diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java new file mode 100644 index 0000000000..dc2fe59e13 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -0,0 +1,17 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils; + +import org.junit.BeforeClass; + +import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; + +/** + * Test class for {@link SQLUtil} + */ +public class SQLUtilTest { + + @BeforeClass + public static void beforeAll() { + + } + +} \ No newline at end of file From 2acc7a13c8ac7072bad55bf33dc1ba3d631ca57f Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Mon, 26 Sep 2022 17:27:23 +0200 Subject: [PATCH 14/39] Testclass for SQL --- .../templates/devon4j/utils/SQLUtil.java | 40 ++++---- .../devon4j/test/utils/SQLUtilTest.java | 10 ++ .../test/utils/resources/TestSqlType.java | 95 +++++++++++++++++++ 3 files changed, 125 insertions(+), 20 deletions(-) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/TestSqlType.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index cc76649efa..469bc55c05 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -44,7 +44,7 @@ public SQLUtil() { * @return returns the equivalent SQL type * @throws IllegalArgumentException when type is not a java type */ - public String mapJavaToSqlType(String canonicalTypeName) throws IllegalArgumentException { + private String mapJavaToSqlType(String canonicalTypeName) throws IllegalArgumentException { try { @@ -330,25 +330,6 @@ public HashMap getPrimaryKeyStatement(Field field) { return column; } - /** - * Helper method to build a hash map for foreign key value pairs - * - * @param name name of the column - * @param type type of the column - * @return HashMap containing name and type of a column - */ - private HashMap columnMapBuild(String name, String type) { - - HashMap columnMap = new HashMap() { - { - put("name", name); - put("type", type); - } - }; - - return columnMap; - } - /** * Get a List of HashMap Keys holding information about a given field * @@ -375,6 +356,25 @@ public HashMap getPrimaryKeyStatement(Field field, String name) return column; } + /** + * Helper method to build a hash map for foreign key value pairs + * + * @param name name of the column + * @param type type of the column + * @return HashMap containing name and type of a column + */ + private HashMap columnMapBuild(String name, String type) { + + HashMap columnMap = new HashMap() { + { + put("name", name); + put("type", type); + } + }; + + return columnMap; + } + /** * Returns the name of a field. When the @Column annotation sets the name option that name will be used. * diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index dc2fe59e13..cbf975c697 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -1,6 +1,9 @@ package com.devonfw.cobigen.templates.devon4j.test.utils; +import java.lang.reflect.Field; + import org.junit.BeforeClass; +import org.junit.Test; import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; @@ -14,4 +17,11 @@ public static void beforeAll() { } + /** + * Tests if {@link SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on a field type + */ + @Test + public void testGetSimpleSQLtype() { + + } } \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/TestSqlType.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/TestSqlType.java new file mode 100644 index 0000000000..5d41d848cb --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/TestSqlType.java @@ -0,0 +1,95 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Blob; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.Month; +import java.time.Year; +import java.util.Calendar; +import java.util.Date; +import java.util.UUID; + +import com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest; + +/** + * This class is a test class for {@link SQLUtilTest} + * + */ +public class TestSqlType { + + // Integer + Integer wrapperInteger; + + int primitiveInt; + + Year year; + + Month month; + + // BIGINT + Long wrapperLong; + + long primitiveLong; + + Object object; + + // SMALLINT + Short wrapperShort; + + short primitiveShort; + + // FLOAT + Float wrapperFloat; + + float primitiveFloat; + + // DOUBLE + Double wrapperDouble; + + double primitiveDouble; + + // NUMERIC + BigDecimal bigDecimal; + + BigInteger bigInteger; + + // CHAR + Character wrapperChar; + + char primitiveChar; + + // TINYINT + Byte wrapperByte; + + byte primitiveByte; + + // BOOLEAN + Boolean wrapperBool; + + boolean primitiveBool; + + // TIMESTAMP + Instant instant; + + Timestamp timestamp; + + // DATE + Date date; + + Calendar calendar; + + // TIME + Time time; + + // BINARY + UUID uuid; + + // BLOB + Blob blob; + + // TODO ADD enum and entity classes for enum sql type and varchar + +} From a6b1613802a12615d0a2fa9fc86471959dc0127c Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Tue, 27 Sep 2022 14:42:14 +0200 Subject: [PATCH 15/39] Added test classes --- .../templates/devon4j/utils/SQLUtil.java | 1 + .../devon4j/test/utils/SQLUtilTest.java | 11 ++++- .../resources/{ => sqltest}/TestSqlType.java | 28 +++++++++++- .../sqltest/TestSqlTypeAnnotations.java | 43 +++++++++++++++++++ .../sqltest/entities/TestSimpleEntity.java | 27 ++++++++++++ .../sqltest/enums/TestSimpleEnum.java | 11 +++++ 6 files changed, 118 insertions(+), 3 deletions(-) rename cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/{ => sqltest}/TestSqlType.java (64%) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/enums/TestSimpleEnum.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 469bc55c05..c57e4b316d 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -525,6 +525,7 @@ public String getForeignKeyStatement(Field field) { type = getSimpleSQLtype(foreignField); } catch (NoSuchFieldException e) { LOG.error("{}: Could not find the field", e.getMessage()); + return null; } return columnName + "," + type; diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index cbf975c697..91ba10378c 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -5,6 +5,7 @@ import org.junit.BeforeClass; import org.junit.Test; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestSqlType; import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; /** @@ -12,16 +13,24 @@ */ public class SQLUtilTest { + private static Class testSqlType; + + /** + * Get all Classes for testing + */ @BeforeClass public static void beforeAll() { + testSqlType = new TestSqlType().getClass(); } /** * Tests if {@link SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on a field type */ @Test - public void testGetSimpleSQLtype() { + public void testGetSimpleSqlType() { + + Field[] fields = testSqlType.getDeclaredFields(); } } \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/TestSqlType.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlType.java similarity index 64% rename from cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/TestSqlType.java rename to cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlType.java index 5d41d848cb..976763075f 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/TestSqlType.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlType.java @@ -1,4 +1,4 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources; +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest; import java.math.BigDecimal; import java.math.BigInteger; @@ -12,7 +12,13 @@ import java.util.Date; import java.util.UUID; +import javax.persistence.Column; +import javax.persistence.Id; + import com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestSimpleEntity; + +import jakarta.validation.constraints.NotNull; /** * This class is a test class for {@link SQLUtilTest} @@ -20,6 +26,23 @@ */ public class TestSqlType { + // Specific Statement variations + @Id + Long id; + + @Column(name = "TEST_AT_COLUMN_NULLABLE", length = 50, nullable = false) + String testAtColumnNullable; + + @Column(name = "TEST_AT_COLUMN_NULLABLE_MISSING", length = 50) + String testAtColumnNullableMissing; + + @Column(name = "TEST_AT_NULLABLE", length = 50) + @NotNull + String testAtNullable; + + @Column(name = "TEST_AT_NULLABLE_MISSING", length = 50) + String testAtNullableMissing; + // Integer Integer wrapperInteger; @@ -90,6 +113,7 @@ public class TestSqlType { // BLOB Blob blob; - // TODO ADD enum and entity classes for enum sql type and varchar + // Entities + TestSimpleEntity testSimpleEntity; } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java new file mode 100644 index 0000000000..97927e5550 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java @@ -0,0 +1,43 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest; + +import javax.persistence.Column; +import javax.persistence.Id; + +import com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestSimpleEntity; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * This class is a test class for {@link SQLUtilTest} + * + */ +public class TestSqlTypeAnnotations { + + // Specific Statement variations + @Id + Long id; + + @Column(name = "TEST_AT_COLUMN_NULLABLE", length = 50, nullable = false) + String testAtColumnNullable; + + @Column(name = "TEST_AT_COLUMN_NULLABLE_MISSING", length = 50) + String testAtColumnNullableMissing; + + @Column(name = "TEST_AT_NULLABLE", length = 50) + @NotNull + String testAtColumnNotNull; + + @Column(name = "TEST_AT_NULLABLE_MISSING", length = 50) + String testAtColumnNotNullMissing; + + // Entities + @Column(name = "TEST_SIMPLE_ENTITY_AT_SIZE", length = 50) + @Size + TestSimpleEntity testSimpleEntityAtSize; + + @Column(name = "TEST_SIMPLE_ENTITY_AT_SIZE_MISSING", length = 50) + TestSimpleEntity testSimpleEntityAtSizeMissing; + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java new file mode 100644 index 0000000000..7358b18853 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java @@ -0,0 +1,27 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import jakarta.validation.constraints.NotNull; + +/** + * This is a simple Entity Class for testing. + * + */ + +@Entity +public class TestSimpleEntity { + + @Id + private Long id; + + @Column(name = "TEST_SIMPLE_NAME", length = 50, nullable = false) + private String name; + + @Column(name = "TEST_SIMPLE_AGE", length = 50) + @NotNull + private Integer age; + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/enums/TestSimpleEnum.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/enums/TestSimpleEnum.java new file mode 100644 index 0000000000..cc636fcff3 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/enums/TestSimpleEnum.java @@ -0,0 +1,11 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.enums; + +/** + * This is an enum for sql testing purposes + * + */ +@SuppressWarnings("javadoc") +public enum TestSimpleEnum { + + ONE, TWO, THREE +} From 97b1c8b80dfa0811686a10ad1cc2921f7824396e Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 7 Oct 2022 16:50:36 +0200 Subject: [PATCH 16/39] Added test cases and refined getsimplesql statement --- .../templates/devon4j/utils/SQLUtil.java | 78 ++++++-- .../devon4j/test/utils/SQLUtilTest.java | 178 +++++++++++++++++- .../utils/resources/sqltest/TestSqlType.java | 119 ------------ .../sqltest/TestSqlTypeAnnotations.java | 41 ++-- 4 files changed, 258 insertions(+), 158 deletions(-) delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlType.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index c57e4b316d..9bf2523e1a 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -8,6 +8,8 @@ import java.util.HashMap; import java.util.List; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.persistence.Column; import javax.persistence.GeneratedValue; @@ -40,19 +42,45 @@ public SQLUtil() { /** * Helper function to map a Java Type to its equivalent SQL type * - * @param canonicalTypeName {@link String} full qualified class name + * @param field {@link Field} of the pojo class * @return returns the equivalent SQL type * @throws IllegalArgumentException when type is not a java type + * @throws IllegalAccessError when the provided field wa */ - private String mapJavaToSqlType(String canonicalTypeName) throws IllegalArgumentException { + private String mapJavaToSqlType(Field field) throws IllegalArgumentException, IllegalAccessError { - try { + // Either a full qualified class name or a simple string like "byte" for primitives + String canonicalTypeName = field.getType().getCanonicalName(); + String javaType = ""; + Boolean isExistingClass = false; + + // Verifies that the provided class exists if it's not a primitive + if (!field.getType().isPrimitive()) { + try { + Class existingClass = Class.forName(canonicalTypeName); + isExistingClass = true; + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException( + "The provided type can not be converted into an SQL type equivalent because the java class doesn't exist!", + e); + } + } + + // Get the class name from *.Byte or Byte + Pattern myClassPattern = Pattern.compile("(\\.|^)((?:.(?!\\.))+)$"); + Matcher myClassMatch = myClassPattern.matcher(canonicalTypeName); + + if (field.getType().isPrimitive() || isExistingClass) { + if (myClassMatch.find()) { + javaType = myClassMatch.group(2); + + } if (isEnum(canonicalTypeName)) { return "INTEGER"; } - switch (canonicalTypeName) { + switch (javaType) { // INTEGER case "Integer": return "INTEGER"; @@ -123,33 +151,37 @@ private String mapJavaToSqlType(String canonicalTypeName) throws IllegalArgument // BLOB case "Blob": return "BLOB"; + // String, Entities default: return "VARCHAR"; } - } catch (IllegalArgumentException e) { - LOG.error("{}: The parameter is not a valid argument", e.getMessage()); - return null; - } + } else { + + throw new IllegalAccessError( + "The provided field is neither an existing class nor an existing primitive type. Cannot generate template as it might obviously depend on reflection."); + } } /** - * Get a basic SQL type statement for primitives. + * Get a basic SQL type statement for wrapper and primitives. * - * @param field field of the pojo class + * @param field {@link Field} of the pojo class * @return SQL statement as {@link String} */ public String getSimpleSQLtype(Field field) { String sqlStatement = ""; - String type = mapJavaToSqlType(field.getType().getCanonicalName()); + String type = mapJavaToSqlType(field); + // getCanonicalNameOfFieldType(field.getDeclaringClass().getCanonicalName(), field.getName()) + sqlStatement += type; if (type.contains("VARCHAR") && field.isAnnotationPresent(Size.class)) { Integer maxSize = field.getAnnotation(Size.class).max(); sqlStatement = sqlStatement + "(" + maxSize.toString() + ")"; } - if (field.isAnnotationPresent(Column.class) && !field.getAnnotation(Column.class).nullable() + if (field.isAnnotationPresent(Column.class) && field.getAnnotation(Column.class).nullable() || field.isAnnotationPresent(NotNull.class)) { sqlStatement = sqlStatement + " NOT NULL"; } @@ -161,7 +193,7 @@ public String getSimpleSQLtype(Field field) { * Helper methods to get all fields recursively including fields from super classes * * @param fields list of fields to be accumulated during recursion - * @param cl class to find fields + * @param cl class to find declared fields * @return list of all fields */ private static List getAllFields(List fields, Class cl) { @@ -181,8 +213,9 @@ private static List getAllFields(List fields, Class cl) { * @param pojoClass {@link Class} the class object of the pojo * @param fieldName {@link String} the name of the field * @return return the field object throws IllegalArgumentException + * @throws IllegalAccessError when the pojoClass is null */ - private Field getFieldByName(Class pojoClass, String fieldName) throws IllegalArgumentException { + private Field getFieldByName(Class pojoClass, String fieldName) throws IllegalAccessError { if (pojoClass != null) { // automatically fetches all fields from pojoClass including its super classes @@ -195,8 +228,9 @@ private Field getFieldByName(Class pojoClass, String fieldName) throws Illega return field.get(); } } - LOG.error("Could not find type of field {}", fieldName); - return null; + + throw new IllegalAccessError( + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } /** @@ -263,7 +297,7 @@ public String getEntityTableName(String className) throws ClassNotFoundException * @param className {@link String} full qualified class name * @param fieldName {@link String} the name of the field * @return type of the field in the string - * @throws ClassNotFoundException to Log an error + * @throws ClassNotFoundException when the className is no fully qualified class name */ public String getCanonicalNameOfFieldType(String className, String fieldName) throws ClassNotFoundException { @@ -322,8 +356,11 @@ public String getPrimaryKey(String className) { * * @param field the current pojo field * @return HashMap containing name and type of a column + * @throws ClassNotFoundException + * @throws IllegalArgumentException */ - public HashMap getPrimaryKeyStatement(Field field) { + public HashMap getPrimaryKeyStatement(Field field) + throws IllegalArgumentException, ClassNotFoundException { String name = getColumnName(field); HashMap column = getPrimaryKeyStatement(field, name); @@ -336,8 +373,11 @@ public HashMap getPrimaryKeyStatement(Field field) { * @param field the current pojo field * @param name the column name related to that field * @return HashMap containing name and type of a column + * @throws ClassNotFoundException + * @throws IllegalArgumentException */ - public HashMap getPrimaryKeyStatement(Field field, String name) { + public HashMap getPrimaryKeyStatement(Field field, String name) + throws IllegalArgumentException, ClassNotFoundException { String type = getSimpleSQLtype(field); diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index 91ba10378c..10ba4defad 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -1,11 +1,13 @@ package com.devonfw.cobigen.templates.devon4j.test.utils; +import static org.assertj.core.api.Assertions.assertThat; + import java.lang.reflect.Field; import org.junit.BeforeClass; import org.junit.Test; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestSqlType; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestSqlTypeAnnotations; import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; /** @@ -13,24 +15,186 @@ */ public class SQLUtilTest { - private static Class testSqlType; + private static Class testSqlTypeAnnotations; + + private static Field fieldTestSimpleString, fieldTestAtSize, fieldTestSizeMissing, fieldTestSimpleInteger, + fieldTestAtColumnNullableAtNotNull, fieldTestAtColumnNullable, fieldTestAtColumnNotNullableAtNotNull, + fieldTestAtColumnNotNullable, fieldTestAtNotNull, fieldTestAtSizeAtNotNull, + fieldTestEntityAtColumnNotNullableAtSizeAtNotNull; /** * Get all Classes for testing + * + * @throws SecurityException + * @throws NoSuchFieldException */ + @SuppressWarnings("javadoc") @BeforeClass - public static void beforeAll() { + public static void beforeAll() throws NoSuchFieldException, SecurityException { + + testSqlTypeAnnotations = new TestSqlTypeAnnotations().getClass(); + fieldTestSimpleString = testSqlTypeAnnotations.getDeclaredField("testSimpleString"); + fieldTestAtSize = testSqlTypeAnnotations.getDeclaredField("testAtSize"); + fieldTestSizeMissing = testSqlTypeAnnotations.getDeclaredField("testSizeMissing"); + fieldTestSimpleInteger = testSqlTypeAnnotations.getDeclaredField("testSimpleInteger"); + fieldTestAtColumnNullableAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtColumnNullableAtNotNull"); + fieldTestAtColumnNullable = testSqlTypeAnnotations.getDeclaredField("testAtColumnNullable"); + fieldTestAtColumnNotNullableAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtColumnNotNullableAtNotNull"); + fieldTestAtColumnNotNullable = testSqlTypeAnnotations.getDeclaredField("testAtColumnNotNullable"); + fieldTestAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtNotNull"); + fieldTestAtSizeAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtSizeAtNotNull"); + fieldTestAtSizeAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtSizeAtNotNull"); + fieldTestEntityAtColumnNotNullableAtSizeAtNotNull = testSqlTypeAnnotations + .getDeclaredField("testEntityAtColumnNotNullableAtSizeAtNotNull"); + + } + + /** + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current field + * is translated to the SQL Type VARCHAR. + * + */ + @Test + public void testGetSimpleSqlTypeAnnotationForStringType() { + + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestSimpleString)).isEqualTo("VARCHAR"); + } + + /** + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current field + * is translated to the SQL Type VARCHAR(SIZE) when the field is provided with the annotation + * {@linkplain jakarta.validation.constraints.Size @Size}. + * + */ + @Test + public void testGetSimpleSqlTypeAnnotationIsVarcharSize() { + + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtSize)).isEqualTo("VARCHAR(2147483647)"); + } + + /** + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current field + * is translated to the SQL Type INTEGER and the field is provided with the annotation + * {@linkplain jakarta.validation.constraints.Size}. + * + * This scenario shows that the generation ignores the {@linkplain jakarta.validation.constraints.Size} annotation + * when the current Java type is not a {@linkplain String}. + * + */ + @Test + public void testGetSimpleSqlTypeAnnotationSizeIsMissing() { + + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestSizeMissing)).isEqualTo("INTEGER"); + } + + /** + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current field + * is translated to the SQL Type INTEGER. + * + */ + @Test + public void testGetSimpleSqlTypeAnnotationForIntegerType() { - testSqlType = new TestSqlType().getClass(); + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestSimpleInteger)).isEqualTo("INTEGER"); } /** - * Tests if {@link SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on a field type + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotations + * {@linkplain javax.persistence.Column @Column} and {@linkplain jakarta.validation.constraints.NotNull @NotNull}. + * When either {@linkplain javax.persistence.Column#nullable nullable} from + * {@linkplain javax.persistence.Column @Column} is set to {@linkplain java.lang.Boolean#TRUE true} or the in this + * case redundant annotation {@linkplain jakarta.validation.constraints.NotNull @NotNull} is provided. The return + * string should contain NOT NULL. + * */ @Test - public void testGetSimpleSqlType() { + public void testGetSimpleSqlTypeAnnotationAtColumnNullableAtNotNull() { + + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtColumnNullableAtNotNull)).isEqualTo("VARCHAR NOT NULL"); + } - Field[] fields = testSqlType.getDeclaredFields(); + /** + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotation + * {@linkplain javax.persistence.Column @Column}. When {@linkplain javax.persistence.Column#nullable nullable} from + * {@linkplain javax.persistence.Column @Column} is set to {@linkplain java.lang.Boolean#TRUE true} the return string + * should contain NOT NULL. + * + */ + @Test + public void testGetSimpleSqlTypeAnnotationAtColumnNullable() { + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtColumnNullable)).isEqualTo("VARCHAR NOT NULL"); } + + /** + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotations + * {@linkplain javax.persistence.Column @Column} and {@linkplain jakarta.validation.constraints.NotNull @NotNull}. + * When either {@linkplain javax.persistence.Column#nullable nullable} from + * {@linkplain javax.persistence.Column @Column} is set to {@linkplain java.lang.Boolean#FALSE false} or the in this + * case redundant annotation {@linkplain jakarta.validation.constraints.NotNull @NotNull} is provided the return + * string should contain NOT NULL. + * + */ + @Test + public void testGetSimpleSqlTypeAnnotationAtColumnNotNullableAtNotNull() { + + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtColumnNotNullableAtNotNull)).isEqualTo("VARCHAR NOT NULL"); + } + + /** + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotation + * {@linkplain javax.persistence.Column @Column} when the {@linkplain javax.persistence.Column#nullable nullable} + * option is set to {@linkplain java.lang.Boolean#FALSE false}. The return string should not contain NOT NULL. + * + */ + @Test + public void testGetSimpleSqlTypeAnnotationAtColumnNotNullable() { + + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtColumnNotNullable)).isEqualTo("VARCHAR"); + } + + /** + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotation + * {@linkplain jakarta.validation.constraints.NotNull @NotNull}. The return string should not contain NOT NULL. + * + */ + @Test + public void testGetSimpleSqlTypeAnnotationAtNotNull() { + + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtNotNull)).isEqualTo("VARCHAR NOT NULL"); + } + + /** + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotations + * {@linkplain jakarta.validation.constraints.Size @Size} and + * {@linkplain jakarta.validation.constraints.NotNull @NotNull}. The default value of + * {@linkplain jakarta.validation.constraints.Size#max() max(} from + * {@linkplain jakarta.validation.constraints.Size @Size} defaults to {@linkplain java.lang.Integer#MAX_VALUE + * Integer.max()} and therefore the result VARCHAR(2147483647) NOT NULL is expected. + * + * + */ + @Test + public void testGetSimpleSqlTypeAnnotationAtSizeAtNotNull() { + + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtSizeAtNotNull)).isEqualTo("VARCHAR(2147483647) NOT NULL"); + } + + /** + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotations + * {@linkplain javax.persistence.Column @Column}, {@linkplain jakarta.validation.constraints.Size @Size} and + * {@linkplain jakarta.validation.constraints.NotNull @NotNull}. The default value of + * {@linkplain jakarta.validation.constraints.Size#max() max(} from + * {@linkplain jakarta.validation.constraints.Size @Size} defaults to {@linkplain java.lang.Integer#MAX_VALUE + * Integer.max()} and therefore the result VARCHAR(2147483647) NOT NULL is expected. + * + * + */ + @Test + public void testGetSimpleSqlTypeAnnotationAtColumnNotNullableAtSizeAtNotNull() { + + assertThat(new SQLUtil().getSimpleSQLtype(fieldTestEntityAtColumnNotNullableAtSizeAtNotNull)) + .isEqualTo("VARCHAR(2147483647) NOT NULL"); + } + } \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlType.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlType.java deleted file mode 100644 index 976763075f..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlType.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.sql.Blob; -import java.sql.Time; -import java.sql.Timestamp; -import java.time.Instant; -import java.time.Month; -import java.time.Year; -import java.util.Calendar; -import java.util.Date; -import java.util.UUID; - -import javax.persistence.Column; -import javax.persistence.Id; - -import com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestSimpleEntity; - -import jakarta.validation.constraints.NotNull; - -/** - * This class is a test class for {@link SQLUtilTest} - * - */ -public class TestSqlType { - - // Specific Statement variations - @Id - Long id; - - @Column(name = "TEST_AT_COLUMN_NULLABLE", length = 50, nullable = false) - String testAtColumnNullable; - - @Column(name = "TEST_AT_COLUMN_NULLABLE_MISSING", length = 50) - String testAtColumnNullableMissing; - - @Column(name = "TEST_AT_NULLABLE", length = 50) - @NotNull - String testAtNullable; - - @Column(name = "TEST_AT_NULLABLE_MISSING", length = 50) - String testAtNullableMissing; - - // Integer - Integer wrapperInteger; - - int primitiveInt; - - Year year; - - Month month; - - // BIGINT - Long wrapperLong; - - long primitiveLong; - - Object object; - - // SMALLINT - Short wrapperShort; - - short primitiveShort; - - // FLOAT - Float wrapperFloat; - - float primitiveFloat; - - // DOUBLE - Double wrapperDouble; - - double primitiveDouble; - - // NUMERIC - BigDecimal bigDecimal; - - BigInteger bigInteger; - - // CHAR - Character wrapperChar; - - char primitiveChar; - - // TINYINT - Byte wrapperByte; - - byte primitiveByte; - - // BOOLEAN - Boolean wrapperBool; - - boolean primitiveBool; - - // TIMESTAMP - Instant instant; - - Timestamp timestamp; - - // DATE - Date date; - - Calendar calendar; - - // TIME - Time time; - - // BINARY - UUID uuid; - - // BLOB - Blob blob; - - // Entities - TestSimpleEntity testSimpleEntity; - -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java index 97927e5550..995087ba6a 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java @@ -19,25 +19,40 @@ public class TestSqlTypeAnnotations { @Id Long id; - @Column(name = "TEST_AT_COLUMN_NULLABLE", length = 50, nullable = false) - String testAtColumnNullable; + String testSimpleString; + + @Size + String testAtSize; + + @Size + Integer testSizeMissing; + + Integer testSimpleInteger; + + @Column(name = "TEST_AT_COLUMN_NULLABLE_AT_NOTNULL", length = 50, nullable = true) + @NotNull + String testAtColumnNullableAtNotNull; - @Column(name = "TEST_AT_COLUMN_NULLABLE_MISSING", length = 50) - String testAtColumnNullableMissing; + @Column(name = "TEST_AT_COLUMN_NULLABLE", length = 50, nullable = true) + String testAtColumnNullable; - @Column(name = "TEST_AT_NULLABLE", length = 50) + @Column(name = "TEST_AT_COLUMN_AT_NOTNULL", length = 50, nullable = false) @NotNull - String testAtColumnNotNull; + String testAtColumnNotNullableAtNotNull; - @Column(name = "TEST_AT_NULLABLE_MISSING", length = 50) - String testAtColumnNotNullMissing; + @Column(name = "TEST_AT_COLUMN", length = 50, nullable = false) + String testAtColumnNotNullable; + + @NotNull + String testAtNotNull; - // Entities - @Column(name = "TEST_SIMPLE_ENTITY_AT_SIZE", length = 50) @Size - TestSimpleEntity testSimpleEntityAtSize; + @NotNull + String testAtSizeAtNotNull; - @Column(name = "TEST_SIMPLE_ENTITY_AT_SIZE_MISSING", length = 50) - TestSimpleEntity testSimpleEntityAtSizeMissing; + @Column(name = "TEST_AT_COLUMN", length = 50, nullable = false) + @Size + @NotNull + TestSimpleEntity testEntityAtColumnNotNullableAtSizeAtNotNull; } From 90ec972174b4fc0799fbe417a002aba0b2a7cb1f Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 7 Oct 2022 18:00:59 +0200 Subject: [PATCH 17/39] More tests --- .../templates/devon4j/utils/SQLUtil.java | 23 ++------- .../devon4j/test/utils/SQLUtilTest.java | 50 +++++++++++++++++++ .../utils/resources/sqltest/TestAnimal.java | 36 +++++++++++++ .../test/utils/resources/sqltest/TestCat.java | 35 +++++++++++++ 4 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 9bf2523e1a..c6289c2d78 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -215,7 +215,7 @@ private static List getAllFields(List fields, Class cl) { * @return return the field object throws IllegalArgumentException * @throws IllegalAccessError when the pojoClass is null */ - private Field getFieldByName(Class pojoClass, String fieldName) throws IllegalAccessError { + public Field getFieldByName(Class pojoClass, String fieldName) throws IllegalAccessError { if (pojoClass != null) { // automatically fetches all fields from pojoClass including its super classes @@ -226,6 +226,9 @@ private Field getFieldByName(Class pojoClass, String fieldName) throws Illega if (field.isPresent()) { return field.get(); + } else { + throw new IllegalAccessError( + "This field doesn't exist. Cannot generate template as it might obviously depend on reflection."); } } @@ -233,22 +236,6 @@ private Field getFieldByName(Class pojoClass, String fieldName) throws Illega "Class object is null. Cannot generate template as it might obviously depend on reflection."); } - /** - * Method to retrieve the type of a field - * - * @param pojoClass {@link Class} the class object of the pojo - * @param fieldName {@link String} the name of the field - * @return return the type of the field - */ - public Class getTypeOfField(Class pojoClass, String fieldName) { - - if (pojoClass != null) { - Field field = getFieldByName(pojoClass, fieldName); - return field.getType(); - } - return null; - } - /** * Method to retrieve the annotations of a field * @@ -303,7 +290,7 @@ public String getCanonicalNameOfFieldType(String className, String fieldName) th try { Class entityClass = Class.forName(className); - Class type = getTypeOfField(entityClass, fieldName); + Class type = getFieldByName(entityClass, fieldName).getType(); if (type != null) { return type.getCanonicalName(); } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index 10ba4defad..6230387a6f 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -1,12 +1,14 @@ package com.devonfw.cobigen.templates.devon4j.test.utils; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; import java.lang.reflect.Field; import org.junit.BeforeClass; import org.junit.Test; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestCat; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestSqlTypeAnnotations; import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; @@ -15,13 +17,21 @@ */ public class SQLUtilTest { + // Annotation test class private static Class testSqlTypeAnnotations; + // Annotation test fields private static Field fieldTestSimpleString, fieldTestAtSize, fieldTestSizeMissing, fieldTestSimpleInteger, fieldTestAtColumnNullableAtNotNull, fieldTestAtColumnNullable, fieldTestAtColumnNotNullableAtNotNull, fieldTestAtColumnNotNullable, fieldTestAtNotNull, fieldTestAtSizeAtNotNull, fieldTestEntityAtColumnNotNullableAtSizeAtNotNull; + // Class and Field lookup test class + private static Class testTestCatClass; + + // Field test fields for lookup comparison + private static Field testResultLegs; + /** * Get all Classes for testing * @@ -32,7 +42,9 @@ public class SQLUtilTest { @BeforeClass public static void beforeAll() throws NoSuchFieldException, SecurityException { + // Annotation class testSqlTypeAnnotations = new TestSqlTypeAnnotations().getClass(); + // Annotation fields fieldTestSimpleString = testSqlTypeAnnotations.getDeclaredField("testSimpleString"); fieldTestAtSize = testSqlTypeAnnotations.getDeclaredField("testAtSize"); fieldTestSizeMissing = testSqlTypeAnnotations.getDeclaredField("testSizeMissing"); @@ -47,6 +59,12 @@ public static void beforeAll() throws NoSuchFieldException, SecurityException { fieldTestEntityAtColumnNotNullableAtSizeAtNotNull = testSqlTypeAnnotations .getDeclaredField("testEntityAtColumnNotNullableAtSizeAtNotNull"); + // Class and Field lookup class + testTestCatClass = new TestCat("Molly", 15).getClass(); + + // Field test fields for lookup comparison + testResultLegs = testTestCatClass.getDeclaredField("legs"); + } /** @@ -197,4 +215,36 @@ public void testGetSimpleSqlTypeAnnotationAtColumnNotNullableAtSizeAtNotNull() { .isEqualTo("VARCHAR(2147483647) NOT NULL"); } + /** + * Tests if {@linkplain SQLUtil#getFieldByName(Class, String)} can lookup for a field in a certain class by its name. + */ + @Test + public void testGetFieldByNameSuccess() { + + assertThat(new SQLUtil().getFieldByName(testTestCatClass, "legs")).isEqualTo(testResultLegs); + } + + /** + * Tests if {@linkplain SQLUtil#getFieldByName(Class, String)} throws an {@linkplain java.lang.IllegalAccessError + * Error} when the field doesn't exist. + */ + @Test + public void testGetFieldByNameThrowsIllegalAccessErrorByField() { + + assertThrows(IllegalAccessError.class, () -> { + new SQLUtil().getFieldByName(testTestCatClass, null); + }); + } + + /** + * Tests if {@linkplain SQLUtil#getFieldByName(Class, String)} throws an {@linkplain java.lang.IllegalAccessError + * Error} when the class doesn't exist. + */ + @Test + public void testGetFieldByNameThrowsIllegalAccessErrorByClass() { + + assertThrows(IllegalAccessError.class, () -> { + new SQLUtil().getFieldByName(null, null); + }); + } } \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java new file mode 100644 index 0000000000..41d81223af --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java @@ -0,0 +1,36 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest; + +import javax.persistence.Id; + +/** + * This is a parent class for all animals for testing purposes. + * + */ +public class TestAnimal { + @Id + Long id; + + private String name; + + public TestAnimal(String name) { + + this.name = name; + } + + /** + * @return name + */ + public String getName() { + + return this.name; + } + + /** + * @param name new value of {@link #getName}. + */ + public void setName(String name) { + + this.name = name; + } + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java new file mode 100644 index 0000000000..adf723839c --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java @@ -0,0 +1,35 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest; + +/** + * This is a child animal class for testing purposes. + * + */ +public class TestCat extends TestAnimal { + private Integer legs; + + /** + * The constructor. + */ + public TestCat(String name, Integer legs) { + + super(name); + this.legs = legs; + } + + /** + * @return legs + */ + public Integer getLegs() { + + return this.legs; + } + + /** + * @param legs new value of {@link #getLegs}. + */ + public void setLegs(Integer legs) { + + this.legs = legs; + } + +} From 4379a548495d615b07be63b3b64f7e53a3116d3c Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Mon, 10 Oct 2022 19:17:17 +0200 Subject: [PATCH 18/39] Implemented utility tests and refined methods --- .../templates/devon4j/utils/SQLUtil.java | 251 +++++++++--------- .../devon4j/test/utils/SQLUtilTest.java | 162 ++++++++++- .../utils/resources/sqltest/TestAnimal.java | 7 + .../test/utils/resources/sqltest/TestCat.java | 1 + .../sqltest/TestSqlTypeAnnotations.java | 34 +++ .../entities/TestAnotherSimpleEntity.java | 49 ++++ .../entities/TestNotSoSimpleEntity.java | 47 ++++ .../sqltest/entities/TestSimpleEntity.java | 22 ++ 8 files changed, 438 insertions(+), 135 deletions(-) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestNotSoSimpleEntity.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index c6289c2d78..b6fecf7e46 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -40,24 +40,21 @@ public SQLUtil() { } /** - * Helper function to map a Java Type to its equivalent SQL type + * Returns the name of a java type as a {@link String} * - * @param field {@link Field} of the pojo class - * @return returns the equivalent SQL type - * @throws IllegalArgumentException when type is not a java type - * @throws IllegalAccessError when the provided field wa + * @param field + * @return */ - private String mapJavaToSqlType(Field field) throws IllegalArgumentException, IllegalAccessError { + private String getSimpleJavaTypeString(Field field) { // Either a full qualified class name or a simple string like "byte" for primitives String canonicalTypeName = field.getType().getCanonicalName(); - String javaType = ""; Boolean isExistingClass = false; // Verifies that the provided class exists if it's not a primitive if (!field.getType().isPrimitive()) { try { - Class existingClass = Class.forName(canonicalTypeName); + Class.forName(canonicalTypeName); isExistingClass = true; } catch (ClassNotFoundException e) { throw new IllegalArgumentException( @@ -72,94 +69,105 @@ private String mapJavaToSqlType(Field field) throws IllegalArgumentException, Il if (field.getType().isPrimitive() || isExistingClass) { if (myClassMatch.find()) { - javaType = myClassMatch.group(2); + return myClassMatch.group(2); } - if (isEnum(canonicalTypeName)) { - return "INTEGER"; - } + } + throw new IllegalAccessError( + "The provided field is neither an existing class nor an existing primitive type. Cannot generate template as it might obviously depend on reflection."); + } - switch (javaType) { - // INTEGER - case "Integer": - return "INTEGER"; - case "int": - return "INTEGER"; - case "Year": - return "INTEGER"; - case "Month": - return "INTEGER"; - // BIGINT - case "Long": - return "BIGINT"; - case "long": - return "BIGINT"; - case "Object": - return "BIGINT"; - // SMALLINT - case "Short": - return "SMALLINT"; - case "short": - return "SMALLINT"; - // FLOAT - case "Float": - return "FLOAT"; - case "float": - return "FLOAT"; - // DOUBLE - case "Double": - return "DOUBLE"; - case "double": - return "DOUBLE"; - // NUMERIC - case "BigDecimal": - return "NUMERIC"; - case "BigInteger": - return "NUMERIC"; - // CHAR - case "Character": - return "CHAR"; - case "char": - return "CHAR"; - // TINYINT - case "Byte": - return "TINYINT"; - case "byte": - return "TINYINT"; - // BOOLEAN - case "Boolean": - return "BOOLEAN"; - case "boolean": - return "BOOLEAN"; - // TIMESTAMP - case "Instant": - return "TIMESTAMP"; - case "Timestamp": - return "TIMESTAMP"; - // DATE - case "Date": - return "DATE"; - case "Calendar": - return "DATE"; - // TIME - case "Time": - return "TIME"; - // BINARY - case "UUID": - return "BINARY"; - // BLOB - case "Blob": - return "BLOB"; - // String, Entities - default: - return "VARCHAR"; - } - } else { + /** + * Helper function to map a Java Type to its equivalent SQL type + * + * @param field {@link Field} of the pojo class + * @return returns the equivalent SQL type + * @throws IllegalArgumentException when type is not a java type + * @throws IllegalAccessError when the provided field wa + */ + private String mapJavaToSqlType(Field field) throws IllegalArgumentException, IllegalAccessError { - throw new IllegalAccessError( - "The provided field is neither an existing class nor an existing primitive type. Cannot generate template as it might obviously depend on reflection."); + String javaType = getSimpleJavaTypeString(field); + if (isEnum(field.getType().getCanonicalName())) { + return "INTEGER"; + } + + switch (javaType) { + // INTEGER + case "Integer": + return "INTEGER"; + case "int": + return "INTEGER"; + case "Year": + return "INTEGER"; + case "Month": + return "INTEGER"; + // BIGINT + case "Long": + return "BIGINT"; + case "long": + return "BIGINT"; + case "Object": + return "BIGINT"; + // SMALLINT + case "Short": + return "SMALLINT"; + case "short": + return "SMALLINT"; + // FLOAT + case "Float": + return "FLOAT"; + case "float": + return "FLOAT"; + // DOUBLE + case "Double": + return "DOUBLE"; + case "double": + return "DOUBLE"; + // NUMERIC + case "BigDecimal": + return "NUMERIC"; + case "BigInteger": + return "NUMERIC"; + // CHAR + case "Character": + return "CHAR"; + case "char": + return "CHAR"; + // TINYINT + case "Byte": + return "TINYINT"; + case "byte": + return "TINYINT"; + // BOOLEAN + case "Boolean": + return "BOOLEAN"; + case "boolean": + return "BOOLEAN"; + // TIMESTAMP + case "Instant": + return "TIMESTAMP"; + case "Timestamp": + return "TIMESTAMP"; + // DATE + case "Date": + return "DATE"; + case "Calendar": + return "DATE"; + // TIME + case "Time": + return "TIME"; + // BINARY + case "UUID": + return "BINARY"; + // BLOB + case "Blob": + return "BLOB"; + // String, Entities + default: + return "VARCHAR"; } } @@ -173,7 +181,6 @@ public String getSimpleSQLtype(Field field) { String sqlStatement = ""; String type = mapJavaToSqlType(field); - // getCanonicalNameOfFieldType(field.getDeclaringClass().getCanonicalName(), field.getName()) sqlStatement += type; if (type.contains("VARCHAR") && field.isAnnotationPresent(Size.class)) { @@ -245,59 +252,54 @@ public Field getFieldByName(Class pojoClass, String fieldName) throws Illegal */ public Annotation[] getFieldAnnotations(Class pojoClass, String fieldName) { + if (fieldName.isBlank() || fieldName == null) { + throw new IllegalAccessError( + "It is not possible to look for a non existing field. Cannot generate template as it might obviously depend on reflection."); + } + if (pojoClass != null) { Annotation[] annotations; Field field = getFieldByName(pojoClass, fieldName); annotations = field.getAnnotations(); return annotations; } - return null; + throw new IllegalAccessError( + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } /** * Get the annotated table name of a given Entity class * - * @param className {@link String} full qualified class name + * @param field {@link Field} of the pojo class * @return return the annotated table name if * @throws ClassNotFoundException to Log an error */ - public String getEntityTableName(String className) throws ClassNotFoundException { + public String getEntityTableName(Field field) throws ClassNotFoundException { - if (!className.endsWith("Entity")) { - LOG.error("Could not return table name because {} is not an Entity class", className); - return null; + if (!field.getType().getCanonicalName().endsWith("Entity")) { + throw new IllegalAccessError("The field " + field.getName() + + " is not an entity class. Cannot generate template as it might obviously depend on reflection."); } try { - Class entityClass = Class.forName(className); + + Class entityClass = Class.forName(field.getType().getCanonicalName()); Table table = entityClass.getAnnotation(Table.class); - return table == null - ? StringUtils.left(entityClass.getSimpleName(), entityClass.getSimpleName().length() - "Entity".length()) - : table.name(); - } catch (ClassNotFoundException e) { - LOG.error("{}: Could not find {}", e.getMessage(), className); - return null; - } - } - /** - * @param className {@link String} full qualified class name - * @param fieldName {@link String} the name of the field - * @return type of the field in the string - * @throws ClassNotFoundException when the className is no fully qualified class name - */ - public String getCanonicalNameOfFieldType(String className, String fieldName) throws ClassNotFoundException { + String javaType = getSimpleJavaTypeString(field); - try { - Class entityClass = Class.forName(className); - Class type = getFieldByName(entityClass, fieldName).getType(); - if (type != null) { - return type.getCanonicalName(); + if (table == null) { + return StringUtils.left(javaType, javaType.length() - "Entity".length()); + } else { + return table.name().isEmpty() ? StringUtils.left(javaType, javaType.length() - "Entity".length()) + : table.name(); } + } catch (ClassNotFoundException e) { - LOG.error("{}: Could not find {}", e.getMessage(), className); + throw new IllegalAccessError( + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } - return null; + } /** @@ -497,16 +499,19 @@ public String getForeignKeyTableName(Field field) { try { String tableName; if (field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToOne.class)) { - tableName = getEntityTableName(field.getType().getCanonicalName()); + tableName = getEntityTableName(field); } else { + // apply regex filter tableName = field.getType().getCanonicalName(); } return tableName; } catch (ClassNotFoundException e) { - LOG.error("{}: Could not find {}", e.getMessage(), field.getType().getCanonicalName()); + throw new IllegalAccessError( + "It is not possible to look for a non existing field. Cannot generate template as it might obviously depend on reflection."); + } - return null; + } /** diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index 6230387a6f..4ebbfdd375 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -3,8 +3,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertThrows; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import org.assertj.core.api.Condition; import org.junit.BeforeClass; import org.junit.Test; @@ -24,7 +26,9 @@ public class SQLUtilTest { private static Field fieldTestSimpleString, fieldTestAtSize, fieldTestSizeMissing, fieldTestSimpleInteger, fieldTestAtColumnNullableAtNotNull, fieldTestAtColumnNullable, fieldTestAtColumnNotNullableAtNotNull, fieldTestAtColumnNotNullable, fieldTestAtNotNull, fieldTestAtSizeAtNotNull, - fieldTestEntityAtColumnNotNullableAtSizeAtNotNull; + fieldTestEntityAtColumnNotNullableAtSizeAtNotNull, fieldTestEntityAtTable, fieldTestAnonymousClassEntity, + fieldTestEntityAtTableNameDefault, fieldTestAnonymousEntityAtTableNameDefault, fieldTestEntityAtTableNull, + fieldTestAnonymousEntityAtTableNull; // Class and Field lookup test class private static Class testTestCatClass; @@ -58,7 +62,13 @@ public static void beforeAll() throws NoSuchFieldException, SecurityException { fieldTestAtSizeAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtSizeAtNotNull"); fieldTestEntityAtColumnNotNullableAtSizeAtNotNull = testSqlTypeAnnotations .getDeclaredField("testEntityAtColumnNotNullableAtSizeAtNotNull"); - + fieldTestEntityAtTable = testSqlTypeAnnotations.getDeclaredField("testEntityAtTable"); + fieldTestAnonymousClassEntity = testSqlTypeAnnotations.getDeclaredField("testAnonymousEntityAtTable"); + fieldTestEntityAtTableNameDefault = testSqlTypeAnnotations.getDeclaredField("testEntityAtTableNameDefault"); + fieldTestAnonymousEntityAtTableNameDefault = testSqlTypeAnnotations + .getDeclaredField("testAnonymousEntityAtTableNameDefault"); + fieldTestEntityAtTableNull = testSqlTypeAnnotations.getDeclaredField("testEntityAtTableNull"); + fieldTestAnonymousEntityAtTableNull = testSqlTypeAnnotations.getDeclaredField("testAnonymousEntityAtTableNull"); // Class and Field lookup class testTestCatClass = new TestCat("Molly", 15).getClass(); @@ -68,8 +78,8 @@ public static void beforeAll() throws NoSuchFieldException, SecurityException { } /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current field - * is translated to the SQL Type VARCHAR. + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current + * {@linkplain java.lang.reflect.Field field} is translated to the SQL Type VARCHAR. * */ @Test @@ -79,8 +89,9 @@ public void testGetSimpleSqlTypeAnnotationForStringType() { } /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current field - * is translated to the SQL Type VARCHAR(SIZE) when the field is provided with the annotation + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current + * {@linkplain java.lang.reflect.Field field} is translated to the SQL Type VARCHAR(SIZE) when the + * {@linkplain java.lang.reflect.Field field} is provided with the annotation * {@linkplain jakarta.validation.constraints.Size @Size}. * */ @@ -91,8 +102,9 @@ public void testGetSimpleSqlTypeAnnotationIsVarcharSize() { } /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current field - * is translated to the SQL Type INTEGER and the field is provided with the annotation + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current + * {@linkplain java.lang.reflect.Field field} is translated to the SQL Type INTEGER and the + * {@linkplain java.lang.reflect.Field field} is provided with the annotation * {@linkplain jakarta.validation.constraints.Size}. * * This scenario shows that the generation ignores the {@linkplain jakarta.validation.constraints.Size} annotation @@ -106,8 +118,8 @@ public void testGetSimpleSqlTypeAnnotationSizeIsMissing() { } /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current field - * is translated to the SQL Type INTEGER. + * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current + * {@linkplain java.lang.reflect.Field field} is translated to the SQL Type INTEGER. * */ @Test @@ -216,7 +228,7 @@ public void testGetSimpleSqlTypeAnnotationAtColumnNotNullableAtSizeAtNotNull() { } /** - * Tests if {@linkplain SQLUtil#getFieldByName(Class, String)} can lookup for a field in a certain class by its name. + * Tests if {@linkplain SQLUtil#getFieldByName(Class, String)} finds a field in a certain class by its name. */ @Test public void testGetFieldByNameSuccess() { @@ -226,7 +238,7 @@ public void testGetFieldByNameSuccess() { /** * Tests if {@linkplain SQLUtil#getFieldByName(Class, String)} throws an {@linkplain java.lang.IllegalAccessError - * Error} when the field doesn't exist. + * Error} when the {@linkplain java.lang.reflect.Field field} doesn't exist. */ @Test public void testGetFieldByNameThrowsIllegalAccessErrorByField() { @@ -247,4 +259,130 @@ public void testGetFieldByNameThrowsIllegalAccessErrorByClass() { new SQLUtil().getFieldByName(null, null); }); } + + /** + * Tests if {@linkplain SQLUtil#getFieldAnnotations(Class, String)} collects a {@linkplain java.lang.reflect.Field + * field's} annotations. + */ + @Test + public void testGetFieldAnnotationsSuccess() { + + final int annotationCountHasElements = 3; + final int annotationCountHasNoElements = 0; + + Condition hasElements = new Condition("A condition to ") { + @Override + public boolean matches(Annotation[] value) { + + return value.length == annotationCountHasElements || value.length == annotationCountHasNoElements; + } + }; + assertThat(new SQLUtil().getFieldAnnotations(testTestCatClass, "name")).has(hasElements); + assertThat(new SQLUtil().getFieldAnnotations(testTestCatClass, "legs")).has(hasElements); + } + + /** + * Tests if {@linkplain SQLUtil#getFieldAnnotations(Class, String)} throws an {@linkplain java.lang.IllegalAccessError + * Error} when the class doesn't exist. + */ + @Test + public void testGetFieldAnnotationsThrowsIllegalAccessErrorByClass() { + + assertThrows(IllegalAccessError.class, () -> { + new SQLUtil().getFieldByName(null, "legs"); + }); + + } + + /** + * Tests if {@linkplain SQLUtil#getFieldAnnotations(Class, String)} throws an {@linkplain java.lang.IllegalAccessError + * Error} when the provided field name can not be searched for. + */ + @Test + public void testGetFieldAnnotationsThrowsIllegalAccessErrorByFieldName() { + + assertThrows(IllegalAccessError.class, () -> { + new SQLUtil().getFieldByName(testTestCatClass, ""); + new SQLUtil().getFieldByName(testTestCatClass, null); + }); + + } + + /** + * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that + * was annotated with the option {@linkplain javax.persistence.Table#name() name} from the + * {@linkplain javax.persistence.Table @Table} annotation. + * + * @throws Exception + */ + @Test + public void testGetEntityTableNameAtTableSuccess() throws Exception { + + assertThat(new SQLUtil().getEntityTableName(fieldTestEntityAtTable)).isEqualTo("TEST_SIMPLE_ENTITY"); + + } + + /** + * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that + * was annotated with the option {@linkplain javax.persistence.Table#name() name} from the + * {@linkplain javax.persistence.Table @Table} annotation even when the provided class is an anonymous class. + */ + @Test + public void testGetEntityTableNameAtTableSuccessAnonymousClasses() throws Exception { + + assertThat(new SQLUtil().getEntityTableName(fieldTestAnonymousClassEntity)).isEqualTo("TEST_SIMPLE_ENTITY"); + + } + + /** + * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that + * was not annotated with the option {@linkplain javax.persistence.Table#name() name} from the + * {@linkplain javax.persistence.Table @Table} annotation. + * + * @throws Exception + */ + @Test + public void testGetEntityTableNameAtTableNameDefaultSuccess() throws Exception { + + assertThat(new SQLUtil().getEntityTableName(fieldTestEntityAtTableNameDefault)).isEqualTo("TestAnotherSimple"); + + } + + /** + * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that + * was not annotated with the option {@linkplain javax.persistence.Table#name() name} from the + * {@linkplain javax.persistence.Table @Table} annotation even when the provided class is an anonymous class. + */ + @Test + public void testGetEntityTableNameAtTableNameDefaultSuccessAnonymousClasses() throws Exception { + + assertThat(new SQLUtil().getEntityTableName(fieldTestAnonymousEntityAtTableNameDefault)) + .isEqualTo("TestAnotherSimple"); + + } + + /** + * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that + * was not annotated with the {@linkplain javax.persistence.Table @Table} annotation. + * + * @throws Exception + */ + @Test + public void testGetEntityTableNameAtTableNullSuccess() throws Exception { + + assertThat(new SQLUtil().getEntityTableName(fieldTestEntityAtTableNull)).isEqualTo("TestNotSoSimple"); + + } + + /** + * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that + * was not annotated with the {@linkplain javax.persistence.Table @Table} annotation even when the provided class is + * an anonymous class. + */ + @Test + public void testGetEntityTableNameAtTableNNullSuccessAnonymousClasses() throws Exception { + + assertThat(new SQLUtil().getEntityTableName(fieldTestAnonymousEntityAtTableNull)).isEqualTo("TestNotSoSimple"); + + } } \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java index 41d81223af..9aee8ed9e8 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java @@ -1,7 +1,11 @@ package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest; +import javax.persistence.Column; import javax.persistence.Id; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + /** * This is a parent class for all animals for testing purposes. * @@ -10,6 +14,9 @@ public class TestAnimal { @Id Long id; + @Column(name = "ANIMAL_NAME", length = 50, nullable = false) + @Size + @NotNull private String name; public TestAnimal(String name) { diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java index adf723839c..e7c078af84 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java @@ -5,6 +5,7 @@ * */ public class TestCat extends TestAnimal { + private Integer legs; /** diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java index 995087ba6a..46dde06e71 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java @@ -4,6 +4,8 @@ import javax.persistence.Id; import com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestAnotherSimpleEntity; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestNotSoSimpleEntity; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestSimpleEntity; import jakarta.validation.constraints.NotNull; @@ -55,4 +57,36 @@ public class TestSqlTypeAnnotations { @NotNull TestSimpleEntity testEntityAtColumnNotNullableAtSizeAtNotNull; + TestSimpleEntity testEntityAtTable; + + TestSimpleEntity testAnonymousEntityAtTable = new TestSimpleEntity("Test", 19) { + @Override + public String testMethod() { + + return "This method will is overwritten"; + } + + }; + + TestAnotherSimpleEntity testEntityAtTableNameDefault; + + TestAnotherSimpleEntity testAnonymousEntityAtTableNameDefault = new TestAnotherSimpleEntity("Test", 19) { + @Override + public String testMethod() { + + return "This method will is overwritten"; + } + + }; + + TestNotSoSimpleEntity testEntityAtTableNull; + + TestNotSoSimpleEntity testAnonymousEntityAtTableNull = new TestNotSoSimpleEntity("Test", 19) { + @Override + public String testMethod() { + + return "This method will is overwritten"; + } + + }; } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java new file mode 100644 index 0000000000..2db1e25f47 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java @@ -0,0 +1,49 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import jakarta.validation.constraints.NotNull; + +/** + * This is a simple Entity Class for testing. + * + */ + +@Entity +@Table(schema = "RECORDS") +public class TestAnotherSimpleEntity { + + @Id + private Long id; + + @Column(name = "TEST_SIMPLE_NAME", length = 50, nullable = false) + private String name; + + @Column(name = "TEST_SIMPLE_AGE", length = 50) + @NotNull + private Integer age; + + /** + * The constructor. + * + * @param name {@link String} of this test entity + * @param age {@link Integer} of this test entity + */ + public TestAnotherSimpleEntity(String name, @NotNull Integer age) { + + this.name = name; + this.age = age; + } + + /** + * @return string + */ + public String testMethod() { + + return "This method will be overwritten"; + } + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestNotSoSimpleEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestNotSoSimpleEntity.java new file mode 100644 index 0000000000..f364c3a90c --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestNotSoSimpleEntity.java @@ -0,0 +1,47 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import jakarta.validation.constraints.NotNull; + +/** + * This is a simple Entity Class for testing. + * + */ + +@Entity +public class TestNotSoSimpleEntity { + + @Id + private Long id; + + @Column(name = "TEST_SIMPLE_NAME", length = 50, nullable = false) + private String name; + + @Column(name = "TEST_SIMPLE_AGE", length = 50) + @NotNull + private Integer age; + + /** + * The constructor. + * + * @param name {@link String} of this test entity + * @param age {@link Integer} of this test entity + */ + public TestNotSoSimpleEntity(String name, @NotNull Integer age) { + + this.name = name; + this.age = age; + } + + /** + * @return string + */ + public String testMethod() { + + return "This method will be overwritten"; + } + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java index 7358b18853..b7446668f1 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java @@ -3,6 +3,7 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.Table; import jakarta.validation.constraints.NotNull; @@ -12,6 +13,7 @@ */ @Entity +@Table(name = "TEST_SIMPLE_ENTITY", schema = "RECORDS") public class TestSimpleEntity { @Id @@ -24,4 +26,24 @@ public class TestSimpleEntity { @NotNull private Integer age; + /** + * The constructor. + * + * @param name {@link String} of this test entity + * @param age {@link Integer} of this test entity + */ + public TestSimpleEntity(String name, @NotNull Integer age) { + + this.name = name; + this.age = age; + } + + /** + * @return string + */ + public String testMethod() { + + return "This method will be overwritten"; + } + } From 6c70fe102648e91125710743f7bf4f8b38d018be Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 14 Oct 2022 12:11:34 +0200 Subject: [PATCH 19/39] Primary key method refined and tests implemented --- .../templates/devon4j/utils/SQLUtil.java | 40 ++++-- .../devon4j/test/utils/SQLUtilTest.java | 123 ++++++++++++++++++ .../utils/resources/sqltest/TestAnimal.java | 3 + .../sqltest/primarykeys/Primaryfive.java | 9 ++ .../sqltest/primarykeys/Primaryfour.java | 19 +++ .../sqltest/primarykeys/Primaryone.java | 12 ++ .../sqltest/primarykeys/Primarythree.java | 17 +++ .../sqltest/primarykeys/Primarytwo.java | 15 +++ 8 files changed, 228 insertions(+), 10 deletions(-) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfour.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryone.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarythree.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index b6fecf7e46..d3beecdbef 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -276,6 +276,10 @@ public Annotation[] getFieldAnnotations(Class pojoClass, String fieldName) { */ public String getEntityTableName(Field field) throws ClassNotFoundException { + if (field == null) { + throw new IllegalAccessError( + "Field object is null. Cannot generate template as it might obviously depend on reflection."); + } if (!field.getType().getCanonicalName().endsWith("Entity")) { throw new IllegalAccessError("The field " + field.getName() + " is not an entity class. Cannot generate template as it might obviously depend on reflection."); @@ -296,8 +300,8 @@ public String getEntityTableName(Field field) throws ClassNotFoundException { } } catch (ClassNotFoundException e) { - throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); + throw new IllegalArgumentException( + "Class path doesn't exist. Cannot generate template as it might obviously depend on reflection.", e); } } @@ -310,14 +314,24 @@ public String getEntityTableName(Field field) throws ClassNotFoundException { */ public String getPrimaryKey(String className) { + if (className == null) { + throw new IllegalAccessError( + "The provides class name can not be null. Cannot generate template as it might obviously depend on reflection."); + } try { Class entityClass = Class.forName(className); List fields = new ArrayList<>(); getAllFields(fields, entityClass); + String columnName = ""; for (Field field : fields) { + if (field.isAnnotationPresent(Id.class)) { - return field.getType().getCanonicalName() + "," - + (field.isAnnotationPresent(Column.class) ? field.getAnnotation(Column.class).name() : field.getName()); + + columnName = field.isAnnotationPresent(Column.class) && !field.getAnnotation(Column.class).name().isBlank() + ? field.getAnnotation(Column.class).name() + : field.getName(); + + return field.getType().getCanonicalName() + "," + columnName; } else { Optional getterOptional = Arrays.stream(entityClass.getMethods()) .filter(m -> m.getName() @@ -325,18 +339,24 @@ public String getPrimaryKey(String className) { && m.isAnnotationPresent(Id.class)) .findFirst(); if (getterOptional.isPresent()) { + Method getter = getterOptional.get(); - return getter.getReturnType().getCanonicalName() + "," - + (getter.isAnnotationPresent(Column.class) ? getter.getAnnotation(Column.class).name() - : field.getName()); + + columnName = getter.isAnnotationPresent(Column.class) + && !getter.getAnnotation(Column.class).name().isBlank() ? getter.getAnnotation(Column.class).name() + : getter.getName(); + + return getter.getReturnType().getCanonicalName() + "," + columnName; } } } } catch (ClassNotFoundException e) { - LOG.error("{}: Could not find {}", e.getMessage(), className); + throw new IllegalArgumentException( + "Class path doesn't exist. Cannot generate template as it might obviously depend on reflection.", e); } - LOG.error("Could not find the field or getter with @Id annotated"); - return null; + + throw new IllegalAccessError( + "Could not find the field or getter with @Id annotated. Cannot generate template as it might obviously depend on reflection."); } /** diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index 4ebbfdd375..b3332b8fa0 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -378,6 +378,8 @@ public void testGetEntityTableNameAtTableNullSuccess() throws Exception { * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that * was not annotated with the {@linkplain javax.persistence.Table @Table} annotation even when the provided class is * an anonymous class. + * + * @throws Exception */ @Test public void testGetEntityTableNameAtTableNNullSuccessAnonymousClasses() throws Exception { @@ -385,4 +387,125 @@ public void testGetEntityTableNameAtTableNNullSuccessAnonymousClasses() throws E assertThat(new SQLUtil().getEntityTableName(fieldTestAnonymousEntityAtTableNull)).isEqualTo("TestNotSoSimple"); } + + /** + * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} throws the Exception . + * + * @throws Exception + */ + @Test + public void testGetEntityTableNameThrowClassNotFoundExceptions() throws Exception { + + assertThrows(IllegalAccessError.class, () -> { + new SQLUtil().getEntityTableName(null); + }); + + } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} returns the correct string when a + * {@linkplain java.lang.reflect.Field field} of the provided class is annotated with the + * {@linkplain javax.persistence.Id @Id} annotation. + * + */ + @Test + public void testGetPrimaryKeyAtIdSuccess() { + + assertThat(new SQLUtil() + .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryone")) + .isEqualTo("java.lang.Long,id"); + + } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} returns the correct string when a + * {@linkplain java.lang.reflect.Field field} of the provided class is annotated with the + * {@linkplain javax.persistence.Id @Id} annotation and the option and {@linkplain javax.persistence.Column#name() + * name} from {@linkplain javax.persistence.Column @Column}. + * + */ + @Test + public void testGetPrimaryKeyAtIdAtColumnSuccess() { + + assertThat(new SQLUtil() + .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primarytwo")) + .isEqualTo("java.lang.Long,TEST_ID"); + + } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} returns the correct string when a method of the provided class + * is annotated with the {@linkplain javax.persistence.Id @Id} annotation. + * + */ + @Test + public void testGetPrimaryKeyMethodAtIdSuccess() { + + assertThat(new SQLUtil() + .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primarythree")) + .isEqualTo("java.lang.Long,getTestId"); + + } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} returns the correct string when a method of the provided class + * is annotated with the {@linkplain javax.persistence.Id @Id} annotation and the option and + * {@linkplain javax.persistence.Column#name() name} from {@linkplain javax.persistence.Column @Column}. + */ + @Test + public void testGetPrimaryKeyMethodAtIdAtColumnSuccess() { + + assertThat(new SQLUtil() + .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryfour")) + .isEqualTo("java.lang.Long,TEST_ID"); + + } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} throws the IllegalArgumentException when the provided class + * doesn't exist . + * + * @throws IllegalArgumentException when non existing class + */ + @Test + public void testGetPrimaryKeyNoClass() throws IllegalArgumentException { + + assertThrows(IllegalArgumentException.class, () -> { + new SQLUtil() + .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.NoClass"); + }); + + } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} throws the IllegalArgumentError when the provided class doesn't + * exist . + * + * @throws IllegalAccessError when class object is null + */ + @Test + public void testGetPrimaryKeyStringNull() throws IllegalAccessError { + + assertThrows(IllegalAccessError.class, () -> { + new SQLUtil().getPrimaryKey(null); + }); + + } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} throws the IllegalArgumentError when the provided class doesn't + * have a {@linkplain java.lang.reflect.Field field} or a method annotated with the + * {@linkplain javax.persistence.Id @Id} annotation. + * + * @throws IllegalAccessError when class object is null + */ + @Test + public void testGetPrimaryKeyNoFieldNoMethod() throws IllegalAccessError { + + assertThrows(IllegalAccessError.class, () -> { + new SQLUtil() + .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryfive"); + }); + + } } \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java index 9aee8ed9e8..0a3cff206c 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java @@ -1,6 +1,8 @@ package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest; import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; import jakarta.validation.constraints.NotNull; @@ -12,6 +14,7 @@ */ public class TestAnimal { @Id + @GeneratedValue(strategy = GenerationType.AUTO) Long id; @Column(name = "ANIMAL_NAME", length = 50, nullable = false) diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java new file mode 100644 index 0000000000..d44da4847b --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java @@ -0,0 +1,9 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; + +/** + * TODO leholm This type ... + * + */ +public class Primaryfive { + private Long id; +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfour.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfour.java new file mode 100644 index 0000000000..a3086e6109 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfour.java @@ -0,0 +1,19 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; + +import javax.persistence.Column; +import javax.persistence.Id; + +/** + * TODO leholm This type ... + * + */ +public class Primaryfour { + private Long testId; + + @Id + @Column(name = "TEST_ID", length = 50, nullable = false) + public Long getTestId() { + + return this.testId; + } +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryone.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryone.java new file mode 100644 index 0000000000..e0f2d84d8a --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryone.java @@ -0,0 +1,12 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; + +import javax.persistence.Id; + +/** + * TODO leholm This type ... + * + */ +public class Primaryone { + @Id + Long id; +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarythree.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarythree.java new file mode 100644 index 0000000000..d0d8b38ea5 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarythree.java @@ -0,0 +1,17 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; + +import javax.persistence.Id; + +/** + * TODO leholm This type ... + * + */ +public class Primarythree { + private Long testId; + + @Id + public Long getTestId() { + + return this.testId; + } +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java new file mode 100644 index 0000000000..31c0e6f3b1 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java @@ -0,0 +1,15 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; + +import javax.persistence.Column; +import javax.persistence.Id; + +/** + * TODO leholm This type ... + * + */ +public class Primarytwo { + @Id + @Column(name = "TEST_ID", length = 50, nullable = false) + private Long id; + +} From ae8c287063aed8f7a9e7c9a6aa25856ab43c055a Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 14 Oct 2022 16:58:56 +0200 Subject: [PATCH 20/39] Primary key tests --- .../templates/devon4j/utils/SQLUtil.java | 117 +++++++----- .../devon4j/test/utils/SQLUtilTest.java | 177 +++++++++++++++++- .../sqltest/TestSqlTypeAnnotations.java | 25 +++ .../sqltest/primarykeys/Primaryfive.java | 5 + .../sqltest/primarykeys/Primarytwo.java | 3 + 5 files changed, 276 insertions(+), 51 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index d3beecdbef..6314e4ec6d 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -306,6 +306,40 @@ public String getEntityTableName(Field field) throws ClassNotFoundException { } + /** + * Returns the name of a field when the @Column annotation sets the name option that name will be used. + * + * @param field the current pojo field + * @return {@link String} + */ + public String getColumnName(Field field) { + + if (field == null) { + throw new IllegalAccessError( + "Field object is null. Cannot generate template as it might obviously depend on reflection."); + } + return field.isAnnotationPresent(Column.class) && !field.getAnnotation(Column.class).name().isBlank() + ? field.getAnnotation(Column.class).name() + : field.getName(); + } + + /** + * Returns the name of a method when the @Column annotation sets the name option that name will be used. + * + * @param method the current method + * @return {@link String} + */ + public String getColumnName(Method method) { + + if (method == null) { + throw new IllegalAccessError( + "Method object is null. Cannot generate template as it might obviously depend on reflection."); + } + return method.isAnnotationPresent(Column.class) && !method.getAnnotation(Column.class).name().isBlank() + ? method.getAnnotation(Column.class).name() + : method.getName(); + } + /** * Get the primary key and its type of a given Entity class * @@ -327,11 +361,7 @@ public String getPrimaryKey(String className) { if (field.isAnnotationPresent(Id.class)) { - columnName = field.isAnnotationPresent(Column.class) && !field.getAnnotation(Column.class).name().isBlank() - ? field.getAnnotation(Column.class).name() - : field.getName(); - - return field.getType().getCanonicalName() + "," + columnName; + return field.getType().getCanonicalName() + "," + getColumnName(field); } else { Optional getterOptional = Arrays.stream(entityClass.getMethods()) .filter(m -> m.getName() @@ -342,11 +372,7 @@ public String getPrimaryKey(String className) { Method getter = getterOptional.get(); - columnName = getter.isAnnotationPresent(Column.class) - && !getter.getAnnotation(Column.class).name().isBlank() ? getter.getAnnotation(Column.class).name() - : getter.getName(); - - return getter.getReturnType().getCanonicalName() + "," + columnName; + return getter.getReturnType().getCanonicalName() + "," + getColumnName(getter); } } } @@ -365,12 +391,13 @@ public String getPrimaryKey(String className) { * * @param field the current pojo field * @return HashMap containing name and type of a column - * @throws ClassNotFoundException - * @throws IllegalArgumentException */ - public HashMap getPrimaryKeyStatement(Field field) - throws IllegalArgumentException, ClassNotFoundException { + public HashMap getPrimaryKeyStatement(Field field) { + if (field == null) { + throw new IllegalAccessError( + "Field object is null. Cannot generate template as it might obviously depend on reflection."); + } String name = getColumnName(field); HashMap column = getPrimaryKeyStatement(field, name); return column; @@ -382,12 +409,17 @@ public HashMap getPrimaryKeyStatement(Field field) * @param field the current pojo field * @param name the column name related to that field * @return HashMap containing name and type of a column - * @throws ClassNotFoundException - * @throws IllegalArgumentException */ - public HashMap getPrimaryKeyStatement(Field field, String name) - throws IllegalArgumentException, ClassNotFoundException { + public HashMap getPrimaryKeyStatement(Field field, String name) { + if (field == null) { + throw new IllegalAccessError( + "Field object is null. Cannot generate template as it might obviously depend on reflection."); + } + + if (name == null || name.isBlank()) { + name = getColumnName(field); + } String type = getSimpleSQLtype(field); if (!type.contains("NOT NULL")) { @@ -424,19 +456,6 @@ private HashMap columnMapBuild(String name, String type) { return columnMap; } - /** - * Returns the name of a field. When the @Column annotation sets the name option that name will be used. - * - * @param field the current pojo field - * @return {@link String} - */ - public String getColumnName(Field field) { - - return field.isAnnotationPresent(Column.class) && !field.getAnnotation(Column.class).name().isBlank() - ? field.getAnnotation(Column.class).name() - : field.getName(); - } - /** * Helper method to build a hash map for foreign key value pairs * @@ -457,6 +476,24 @@ private HashMap fkMapBuild(String name, String table, String col return foreignKeyMap; } + /** + * Get the name of a @JoinColumn or fall back to a default in name in case the name option is not set + * + * @param field current pojo class + * @param fallBack String for fallback option + * @return {@link String} Column name + */ + public String getForeignKeyName(Field field, String fallBack) { + + String name; + if (field.isAnnotationPresent(JoinColumn.class) && !field.getAnnotation(JoinColumn.class).name().isBlank()) { + name = field.getAnnotation(JoinColumn.class).name(); + } else { + name = field.getName() + "_" + fallBack; + } + return name; + } + /** * Get a List of Key and Value pairs holding the information about foreign keys. It will be assumed that the current * field is an entity. This function defaults the {@link String} name parameter of @@ -588,22 +625,4 @@ public String getForeignKeyStatement(Field field) { return null; } - /** - * Get the name of a @JoinColumn or fall back to a default in name in case the name option is not set - * - * @param field current pojo class - * @param fallBack String for fallback option - * @return {@link String} Column name - */ - public String getForeignKeyName(Field field, String fallBack) { - - String name; - if (field.isAnnotationPresent(JoinColumn.class) && !field.getAnnotation(JoinColumn.class).name().isBlank()) { - name = field.getAnnotation(JoinColumn.class).name(); - } else { - name = field.getName() + "_" + fallBack; - } - return name; - } - } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index b3332b8fa0..d2e77580b3 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -5,6 +5,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; import org.assertj.core.api.Condition; import org.junit.BeforeClass; @@ -12,6 +14,11 @@ import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestCat; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestSqlTypeAnnotations; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryfive; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryfour; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryone; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primarythree; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primarytwo; import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; /** @@ -36,15 +43,30 @@ public class SQLUtilTest { // Field test fields for lookup comparison private static Field testResultLegs; + // Class for primary keys + private static Class testPrimaryOne, testPrimaryTwo, testPrimaryThree, testPrimaryFour, testPrimaryFive; + + // Field for primary keys + private static Field fieldTestAtId, fieldTestAtIdAtGeneratedValue; + + // Simple fields + private static Field fieldGetColumnNameFieldAtColumn, fieldGetColumnNameFieldAtColumnBlank, + fieldGetColumnNameFieldAtColumnMissing; + + // Simple methods + private static Method methodGetColumnNameMethodAtColumn, methodGetColumnNameMethodAtColumnBlank, + methodGetColumnNameMethodAtColumnMissing; + /** * Get all Classes for testing * * @throws SecurityException * @throws NoSuchFieldException + * @throws NoSuchMethodException */ @SuppressWarnings("javadoc") @BeforeClass - public static void beforeAll() throws NoSuchFieldException, SecurityException { + public static void beforeAll() throws NoSuchFieldException, SecurityException, NoSuchMethodException { // Annotation class testSqlTypeAnnotations = new TestSqlTypeAnnotations().getClass(); @@ -69,12 +91,31 @@ public static void beforeAll() throws NoSuchFieldException, SecurityException { .getDeclaredField("testAnonymousEntityAtTableNameDefault"); fieldTestEntityAtTableNull = testSqlTypeAnnotations.getDeclaredField("testEntityAtTableNull"); fieldTestAnonymousEntityAtTableNull = testSqlTypeAnnotations.getDeclaredField("testAnonymousEntityAtTableNull"); + fieldGetColumnNameFieldAtColumn = testSqlTypeAnnotations.getDeclaredField("testGetColumnNameFieldAtColumn"); + fieldGetColumnNameFieldAtColumnBlank = testSqlTypeAnnotations + .getDeclaredField("testGetColumnNameFieldAtColumnBlank"); + fieldGetColumnNameFieldAtColumnMissing = testSqlTypeAnnotations + .getDeclaredField("testGetColumnNameFieldAtColumnMissing"); + methodGetColumnNameMethodAtColumn = testSqlTypeAnnotations.getDeclaredMethod("getTestGetColumnNameMethodAtColumn"); + methodGetColumnNameMethodAtColumnBlank = testSqlTypeAnnotations + .getDeclaredMethod("getTestGetColumnNameMethodAtColumnBlank"); + methodGetColumnNameMethodAtColumnMissing = testSqlTypeAnnotations + .getDeclaredMethod("getTestGetColumnNameMethodAtColumnMissing"); // Class and Field lookup class testTestCatClass = new TestCat("Molly", 15).getClass(); // Field test fields for lookup comparison testResultLegs = testTestCatClass.getDeclaredField("legs"); + // Annotation class primary key + testPrimaryOne = new Primaryone().getClass(); + testPrimaryTwo = new Primarytwo().getClass(); + testPrimaryThree = new Primarythree().getClass(); + testPrimaryFour = new Primaryfour().getClass(); + testPrimaryFive = new Primaryfive().getClass(); + // Annotation fields primary key + fieldTestAtId = testPrimaryOne.getDeclaredField("id"); + fieldTestAtIdAtGeneratedValue = testPrimaryTwo.getDeclaredField("id"); } /** @@ -389,7 +430,7 @@ public void testGetEntityTableNameAtTableNNullSuccessAnonymousClasses() throws E } /** - * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} throws the Exception . + * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} throws the Exception IllegalAccessError. * * @throws Exception */ @@ -402,6 +443,74 @@ public void testGetEntityTableNameThrowClassNotFoundExceptions() throws Exceptio } + /** + * Tests if {@linkplain SQLUtil#getColumnName(Field)} returns the correct string based on the value in + * {@linkplain javax.persistence.Column#name() name } of the provided annotation + * {@linkplain javax.persistence.Column @Column}. + */ + @Test + public void testFieldGetColumnNameAtColumn() { + + assertThat(new SQLUtil().getColumnName(fieldGetColumnNameFieldAtColumn)).isEqualTo("FIELD_AT_COLUMN"); + } + + /** + * Tests if {@linkplain SQLUtil#getColumnName(Field)} returns the correct string when the option + * {@linkplain javax.persistence.Column#name() name } of the provided annotation + * {@linkplain javax.persistence.Column @Column} is defaulting to blank. + */ + @Test + public void testFieldGetColumnNameAtColumnBlank() { + + assertThat(new SQLUtil().getColumnName(fieldGetColumnNameFieldAtColumnBlank)) + .isEqualTo("testGetColumnNameFieldAtColumnBlank"); + } + + /** + * Tests if {@linkplain SQLUtil#getColumnName(Field)} returns the correct string when the provided annotation + * {@linkplain javax.persistence.Column @Column} is missing. + */ + @Test + public void testFieldGetColumnNameAtColumnMissing() { + + assertThat(new SQLUtil().getColumnName(fieldGetColumnNameFieldAtColumnMissing)) + .isEqualTo("testGetColumnNameFieldAtColumnMissing"); + } + + /** + * Tests if {@linkplain SQLUtil#getColumnName(Method)} returns the correct string based on the value in + * {@linkplain javax.persistence.Column#name() name } of the provided annotation + * {@linkplain javax.persistence.Column @Column}. + */ + @Test + public void testMethodGetColumnNameAtColumn() { + + assertThat(new SQLUtil().getColumnName(methodGetColumnNameMethodAtColumn)).isEqualTo("METHOD_AT_COLUMN"); + } + + /** + * Tests if {@linkplain SQLUtil#getColumnName(Method)} returns the correct string when the option + * {@linkplain javax.persistence.Column#name() name } of the provided annotation + * {@linkplain javax.persistence.Column @Column} is defaulting to blank. + */ + @Test + public void testMethodGetColumnNameAtColumnBlank() { + + assertThat(new SQLUtil().getColumnName(methodGetColumnNameMethodAtColumnBlank)) + .isEqualTo("getTestGetColumnNameMethodAtColumnBlank"); + } + + /** + * Tests if {@linkplain SQLUtil#getColumnName(Method)} returns the correct string when the provided annotation + * {@linkplain javax.persistence.Column @Column} is missing. + */ + @Test + public void testMethodGetColumnNameAtColumnMissing() { + + assertThat(new SQLUtil().getColumnName(methodGetColumnNameMethodAtColumnMissing)) + .isEqualTo("getTestGetColumnNameMethodAtColumnMissing"); + } + /** * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} returns the correct string when a * {@linkplain java.lang.reflect.Field field} of the provided class is annotated with the @@ -508,4 +617,68 @@ public void testGetPrimaryKeyNoFieldNoMethod() throws IllegalAccessError { }); } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values. + * + */ + @Test + public void testGetPrimaryKeyStatementAtId() { + + HashMap columnMap = new HashMap() { + { + put("name", "id"); + put("type", "BIGINT NOT NULL"); + } + }; + assertThat(new SQLUtil().getPrimaryKeyStatement(fieldTestAtId)).isEqualTo(columnMap); + + } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values. + * + */ + @Test + public void testGetPrimaryKeyStatementAtIdAtGeneratedValue() { + + HashMap columnMap = new HashMap() { + { + put("name", "TEST_ID"); + put("type", "BIGINT NOT NULL AUTO INCREMENT"); + } + }; + assertThat(new SQLUtil().getPrimaryKeyStatement(fieldTestAtIdAtGeneratedValue)).isEqualTo(columnMap); + + } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} throws the IllegalAccessError when the field is null. + */ + @Test + public void testGetPrimaryKeyStatementAtIdThrowsIllegalAccessError() { + + assertThrows(IllegalAccessError.class, () -> { + new SQLUtil().getPrimaryKeyStatement(null); + }); + + } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values even if the name was + * not provided. + * + */ + @Test + public void testGetPrimaryKeyStatementNameNull() { + + HashMap columnMap = new HashMap() { + { + put("name", "TEST_ID"); + put("type", "BIGINT NOT NULL AUTO INCREMENT"); + } + }; + assertThat(new SQLUtil().getPrimaryKeyStatement(fieldTestAtIdAtGeneratedValue, null)).isEqualTo(columnMap); + + } } \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java index 46dde06e71..6f4046edbd 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java @@ -89,4 +89,29 @@ public String testMethod() { } }; + + @Column(name = "FIELD_AT_COLUMN", length = 50, nullable = false) + Integer testGetColumnNameFieldAtColumn; + + @Column(length = 50, nullable = false) + Integer testGetColumnNameFieldAtColumnBlank; + + Integer testGetColumnNameFieldAtColumnMissing; + + @Column(name = "METHOD_AT_COLUMN", length = 50, nullable = false) + public Integer getTestGetColumnNameMethodAtColumn() { + + return this.testGetColumnNameFieldAtColumn; + } + + @Column(length = 50, nullable = false) + public Integer getTestGetColumnNameMethodAtColumnBlank() { + + return this.testGetColumnNameFieldAtColumnBlank; + } + + public Integer getTestGetColumnNameMethodAtColumnMissing() { + + return this.testGetColumnNameFieldAtColumnMissing; + } } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java index d44da4847b..f29c335990 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java @@ -6,4 +6,9 @@ */ public class Primaryfive { private Long id; + + public Long getId() { + + return this.id; + } } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java index 31c0e6f3b1..150aa7dc73 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java @@ -1,6 +1,8 @@ package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; /** @@ -10,6 +12,7 @@ public class Primarytwo { @Id @Column(name = "TEST_ID", length = 50, nullable = false) + @GeneratedValue(strategy = GenerationType.AUTO) private Long id; } From cb3131625ae26d0d99943d3ba6f8be6c02d083e8 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 14 Oct 2022 17:52:56 +0200 Subject: [PATCH 21/39] Foreign Key methods --- .../templates/devon4j/utils/SQLUtil.java | 123 +++++++++--------- .../devon4j/test/utils/SQLUtilTest.java | 30 +++++ .../entities/TestAnotherSimpleEntity.java | 2 +- .../sqltest/entities/TestSimpleEntity.java | 7 + 4 files changed, 103 insertions(+), 59 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 6314e4ec6d..23787da21a 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -485,6 +485,15 @@ private HashMap fkMapBuild(String name, String table, String col */ public String getForeignKeyName(Field field, String fallBack) { + if (field == null) { + throw new IllegalAccessError( + "Field object is null. Cannot generate template as it might obviously depend on reflection."); + } + + if (fallBack == null || fallBack.isBlank()) { + fallBack = "default"; + } + String name; if (field.isAnnotationPresent(JoinColumn.class) && !field.getAnnotation(JoinColumn.class).name().isBlank()) { name = field.getAnnotation(JoinColumn.class).name(); @@ -494,6 +503,57 @@ public String getForeignKeyName(Field field, String fallBack) { return name; } + /** + * Retrieve the name of a referenced column name and its type statement based on the provided field. @JoinColumn has + * precedence over @OneToMany when the field is annotated with both the option referencedColumnName from + * the @JoinColumn annotation and the mappedBy option from the @OneToMany annotation. The method will find the owner + * of the entity if none of the options are provided. This method will mostly be used on Entity types. + * + * @param field current pojo class + * @return comma separated String or null + */ + public String getForeignKeyStatement(Field field) { + + String columnName, type = ""; + + try { + // The current field type is the class in which we are searching the primary key + Class foreignEntityClass = Class.forName(field.getType().getCanonicalName()); + + // Building the column name based on provided annotations + if (field.isAnnotationPresent(JoinColumn.class) + && !field.getAnnotation(JoinColumn.class).referencedColumnName().isEmpty()) { + // Annotation @JoinColumn is set and a name was provided + columnName = field.getAnnotation(JoinColumn.class).referencedColumnName(); + + } else if (field.isAnnotationPresent(OneToOne.class) + && !field.getAnnotation(OneToOne.class).mappedBy().isBlank()) { + // mappedBy option is set by @OneToOne annotation + columnName = field.getAnnotation(OneToOne.class).mappedBy(); + + } else { + // No information was provided and we are looking for the primary key manually + String[] pkReceived = getPrimaryKey(field.getType().getCanonicalName()).split(","); + columnName = pkReceived[1]; + + } + + try { + Field foreignField = foreignEntityClass.getDeclaredField(columnName); + type = getSimpleSQLtype(foreignField); + } catch (NoSuchFieldException e) { + throw new IllegalAccessError( + "Couldn't find the foreign key field. Cannot generate template as it might obviously depend on reflection."); + } + + return columnName + "," + type; + + } catch (ClassNotFoundException e) { + throw new IllegalAccessError( + "Class object is null. Cannot generate template as it might obviously depend on reflection."); + } + } + /** * Get a List of Key and Value pairs holding the information about foreign keys. It will be assumed that the current * field is an entity. This function defaults the {@link String} name parameter of @@ -516,7 +576,8 @@ public HashMap getForeignKeyData(Field field) { return getForeignKeyData(field, name); } - return null; + throw new IllegalAccessError( + "The ForeignKeyData couldn't be generated. Cannot generate template as it might obviously depend on reflection."); } /** @@ -542,7 +603,8 @@ public HashMap getForeignKeyData(Field field, String name) { return foreignKeyData; } - return null; + throw new IllegalAccessError( + "The ForeignKeyData couldn't be generated. Cannot generate template as it might obviously depend on reflection."); } /** @@ -558,8 +620,7 @@ public String getForeignKeyTableName(Field field) { if (field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToOne.class)) { tableName = getEntityTableName(field); } else { - // apply regex filter - tableName = field.getType().getCanonicalName(); + tableName = getSimpleJavaTypeString(field); } return tableName; @@ -571,58 +632,4 @@ public String getForeignKeyTableName(Field field) { } - /** - * Retrieve the name of a referenced column name and its type statement based on the provided field. @JoinColumn has - * precedence over @OneToMany when the field is annotated with both the option referencedColumnName from - * the @JoinColumn annotation and the mappedBy option from the @OneToMany annotation. The method will find the owner - * of the entity if none of the options are provided. This method will mostly be used on Entity types. - * - * @param field current pojo class - * @return comma separated String or null - */ - public String getForeignKeyStatement(Field field) { - - String columnName, type = ""; - - try { - // The current field type is the class in which we are searching the primary key - Class foreignEntityClass = Class.forName(field.getType().getCanonicalName()); - - // Building the column name based on provided annotations - if (field.isAnnotationPresent(JoinColumn.class) - && !field.getAnnotation(JoinColumn.class).referencedColumnName().isEmpty()) { - // Annotation @JoinColumn is set and a name was provided - columnName = field.getAnnotation(JoinColumn.class).referencedColumnName(); - - } else if (field.isAnnotationPresent(OneToOne.class) - && !field.getAnnotation(OneToOne.class).mappedBy().isBlank()) { - // mappedBy option is set by @OneToOne annotation - columnName = field.getAnnotation(OneToOne.class).mappedBy(); - - } else { - // No information was provided and we are looking for the primary key manually - String[] pkReceived = getPrimaryKey(field.getType().getCanonicalName()).split(","); - if (pkReceived.equals(null)) { - return null; - } - columnName = pkReceived[1]; - - } - - try { - Field foreignField = foreignEntityClass.getDeclaredField(columnName); - type = getSimpleSQLtype(foreignField); - } catch (NoSuchFieldException e) { - LOG.error("{}: Could not find the field", e.getMessage()); - return null; - } - - return columnName + "," + type; - - } catch (ClassNotFoundException e) { - LOG.error("{}: Could not find {}", e.getMessage(), field.getType().getCanonicalName()); - } - return null; - } - } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index d2e77580b3..093f057da6 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -14,6 +14,9 @@ import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestCat; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestSqlTypeAnnotations; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestAnotherSimpleEntity; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestNotSoSimpleEntity; +import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestSimpleEntity; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryfive; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryfour; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryone; @@ -57,6 +60,13 @@ public class SQLUtilTest { private static Method methodGetColumnNameMethodAtColumn, methodGetColumnNameMethodAtColumnBlank, methodGetColumnNameMethodAtColumnMissing; + // Foreign Keys + // Classes + private static Class testAnotherSimpleEntity, testNotSoSimpleEntity, testSimpleEntity; + + // Fields + private static Field fieldSimpleEntity; + /** * Get all Classes for testing * @@ -116,6 +126,14 @@ public static void beforeAll() throws NoSuchFieldException, SecurityException, N // Annotation fields primary key fieldTestAtId = testPrimaryOne.getDeclaredField("id"); fieldTestAtIdAtGeneratedValue = testPrimaryTwo.getDeclaredField("id"); + + // Foreign key + // Classes + testAnotherSimpleEntity = new TestAnotherSimpleEntity("TestAnotherSimple", 20).getClass(); + testNotSoSimpleEntity = new TestNotSoSimpleEntity("TestNotSoSimple", 18).getClass(); + testSimpleEntity = new TestSimpleEntity("TestSimple", 15).getClass(); + // Fields + fieldSimpleEntity = testSimpleEntity.getDeclaredField("simpleEntity"); } /** @@ -681,4 +699,16 @@ public void testGetPrimaryKeyStatementNameNull() { assertThat(new SQLUtil().getPrimaryKeyStatement(fieldTestAtIdAtGeneratedValue, null)).isEqualTo(columnMap); } + + /** + * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values even if the name was + * not provided. + * + */ + @Test + public void testgetForeignKeyNameWithAnnotations() { + + assertThat(new SQLUtil().getForeignKeyName(fieldSimpleEntity, "test")).isEqualTo(""); + + } } \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java index 2db1e25f47..d3b9cadc21 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java @@ -17,7 +17,7 @@ public class TestAnotherSimpleEntity { @Id - private Long id; + private Long simpleEntityId; @Column(name = "TEST_SIMPLE_NAME", length = 50, nullable = false) private String name; diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java index b7446668f1..8b14a5180c 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java @@ -2,7 +2,10 @@ import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; import javax.persistence.Table; import jakarta.validation.constraints.NotNull; @@ -26,6 +29,10 @@ public class TestSimpleEntity { @NotNull private Integer age; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "simpleEntityId") + private TestAnotherSimpleEntity simpleEntity; + /** * The constructor. * From 80469814aaf1355fdf0351d91e1fe47c767d0398 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Tue, 25 Oct 2022 17:18:00 +0200 Subject: [PATCH 22/39] implemented isFieldEntity --- .../templates/devon4j/utils/SQLUtil.java | 89 +++++++++++++------ .../SQLAnnotationTest.java} | 72 ++++++++++++--- .../utils/SQLUtilTest/SQLForeignKeyTest.java | 9 ++ .../sqltest/TestSqlTypeAnnotations.java | 4 +- .../sqltest/entities/TestSimpleEntity.java | 4 + 5 files changed, 133 insertions(+), 45 deletions(-) rename cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/{SQLUtilTest.java => SQLUtilTest/SQLAnnotationTest.java} (93%) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLForeignKeyTest.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 23787da21a..d3f4c1071d 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -12,10 +12,12 @@ import java.util.regex.Pattern; import javax.persistence.Column; +import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; import javax.persistence.ManyToOne; import javax.persistence.OneToOne; import javax.persistence.Table; @@ -39,6 +41,24 @@ public SQLUtil() { // Empty for CobiGen to automatically instantiate it } + /** + * @param field + * @return + */ + public Boolean isFieldEntity(Field field) { + + try { + + Class clazz = Class.forName(field.getType().getCanonicalName()); + + return clazz.isAnnotationPresent(Entity.class); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException( + "The declared class of this field type can not be verified as an Entity because the java class does not exist!", + e); + } + } + /** * Returns the name of a java type as a {@link String} * @@ -49,7 +69,7 @@ private String getSimpleJavaTypeString(Field field) { // Either a full qualified class name or a simple string like "byte" for primitives String canonicalTypeName = field.getType().getCanonicalName(); - Boolean isExistingClass = false; + boolean isExistingClass = false; // Verifies that the provided class exists if it's not a primitive if (!field.getType().isPrimitive()) { @@ -356,11 +376,11 @@ public String getPrimaryKey(String className) { Class entityClass = Class.forName(className); List fields = new ArrayList<>(); getAllFields(fields, entityClass); - String columnName = ""; for (Field field : fields) { if (field.isAnnotationPresent(Id.class)) { + // return canonical field name , column name return field.getType().getCanonicalName() + "," + getColumnName(field); } else { Optional getterOptional = Arrays.stream(entityClass.getMethods()) @@ -477,30 +497,30 @@ private HashMap fkMapBuild(String name, String table, String col } /** - * Get the name of a @JoinColumn or fall back to a default in name in case the name option is not set + * Get the name of the Column for the Foreign Key. Extracted from + * {@linkplain javax.persistence.JoinColumn#name() @JoinColumn.name()}. When no name has been provided in the + * Annotation a string with a fallback name will be generated. * * @param field current pojo class * @param fallBack String for fallback option - * @return {@link String} Column name + * @return {@link String} Column name for Foreign Key declaration */ - public String getForeignKeyName(Field field, String fallBack) { + public String getForeignKeyColumnName(Field field, String fallBack) { if (field == null) { - throw new IllegalAccessError( - "Field object is null. Cannot generate template as it might obviously depend on reflection."); + throw new IllegalAccessError("Field object is null. Cannot generate a column name for a foreign key."); } - if (fallBack == null || fallBack.isBlank()) { - fallBack = "default"; - } + if (field.isAnnotationPresent(JoinColumn.class)) { - String name; - if (field.isAnnotationPresent(JoinColumn.class) && !field.getAnnotation(JoinColumn.class).name().isBlank()) { - name = field.getAnnotation(JoinColumn.class).name(); - } else { - name = field.getName() + "_" + fallBack; + if (fallBack == null || fallBack.isBlank()) { + fallBack = "FK"; + } + // A name will be generated based on the option set in @JoinColumn.name() + return !field.getAnnotation(JoinColumn.class).name().isBlank() ? field.getAnnotation(JoinColumn.class).name() + : fallBack + "_" + field.getName(); } - return name; + throw new IllegalAccessError("Field is not annotated with @JoinColumn. Implicit foreign keys are not allowed."); } /** @@ -514,7 +534,7 @@ public String getForeignKeyName(Field field, String fallBack) { */ public String getForeignKeyStatement(Field field) { - String columnName, type = ""; + String foreignKeyOwnedBy = null, type = ""; try { // The current field type is the class in which we are searching the primary key @@ -522,31 +542,42 @@ public String getForeignKeyStatement(Field field) { // Building the column name based on provided annotations if (field.isAnnotationPresent(JoinColumn.class) - && !field.getAnnotation(JoinColumn.class).referencedColumnName().isEmpty()) { + && !field.getAnnotation(JoinColumn.class).referencedColumnName().isBlank()) { // Annotation @JoinColumn is set and a name was provided - columnName = field.getAnnotation(JoinColumn.class).referencedColumnName(); + foreignKeyOwnedBy = field.getAnnotation(JoinColumn.class).referencedColumnName(); } else if (field.isAnnotationPresent(OneToOne.class) && !field.getAnnotation(OneToOne.class).mappedBy().isBlank()) { // mappedBy option is set by @OneToOne annotation - columnName = field.getAnnotation(OneToOne.class).mappedBy(); - - } else { - // No information was provided and we are looking for the primary key manually + foreignKeyOwnedBy = field.getAnnotation(OneToOne.class).mappedBy(); + } else if (field.isAnnotationPresent(JoinColumns.class)) { + + JoinColumn[] joinColumnDeclarations = field.getAnnotation(JoinColumns.class).value(); + // @JoinColumns holds multiple @JoinColumn + // for (JoinColumn joinColumn : joinColumnDeclarations) { + // + // } + + field.getAnnotation(JoinColumns.class).value(); + } else if (field.isAnnotationPresent(JoinColumn.class)) { + // Default values to JoinColumn apply String[] pkReceived = getPrimaryKey(field.getType().getCanonicalName()).split(","); - columnName = pkReceived[1]; + foreignKeyOwnedBy = pkReceived[1]; } try { - Field foreignField = foreignEntityClass.getDeclaredField(columnName); + // Retrieving the field for the calculated coumnName + Field foreignField = foreignEntityClass.getDeclaredField(foreignKeyOwnedBy); + // Field was found and its type will be mapped into the SQL world type = getSimpleSQLtype(foreignField); } catch (NoSuchFieldException e) { throw new IllegalAccessError( "Couldn't find the foreign key field. Cannot generate template as it might obviously depend on reflection."); } - return columnName + "," + type; + // Returning comma separated string so that it can be used in freemarker template with the split method + return foreignKeyOwnedBy + "," + type; } catch (ClassNotFoundException e) { throw new IllegalAccessError( @@ -555,12 +586,12 @@ public String getForeignKeyStatement(Field field) { } /** - * Get a List of Key and Value pairs holding the information about foreign keys. It will be assumed that the current + * Get HashMap of Key and Value pairs holding the information about foreign keys. It will be assumed that the current * field is an entity. This function defaults the {@link String} name parameter of * {@link SQLUtil#getForeignKeyData(Field, String)} * * @param field the pojo field - * @return HashMap holding the information {"name": name, "table": table, "columnname": columnname} or null + * @return HashMap holding the information {"name": name, "table": table, "columnname": columnname} * * @name {@link String} the name of the foreign key * @table {@link String} the table which is referenced by the foreign key @@ -571,7 +602,7 @@ public HashMap getForeignKeyData(Field field) { // Assumes @JoinColumn is present if (field.isAnnotationPresent(JoinColumn.class)) { String[] fkDeclaration = getForeignKeyStatement(field).split(","); - String name = getForeignKeyName(field, fkDeclaration[0]); + String name = getForeignKeyColumnName(field, fkDeclaration[0]); return getForeignKeyData(field, name); } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLAnnotationTest.java similarity index 93% rename from cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java rename to cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLAnnotationTest.java index 093f057da6..0238c4976f 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLAnnotationTest.java @@ -1,4 +1,4 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils; +package com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertThrows; @@ -27,7 +27,7 @@ /** * Test class for {@link SQLUtil} */ -public class SQLUtilTest { +public class SQLAnnotationTest { // Annotation test class private static Class testSqlTypeAnnotations; @@ -65,7 +65,9 @@ public class SQLUtilTest { private static Class testAnotherSimpleEntity, testNotSoSimpleEntity, testSimpleEntity; // Fields - private static Field fieldSimpleEntity; + private static Field fieldSimpleEntity, fieldSimpleEntityDefaultName, fieldTestEntityExisting; + + private static TestSimpleEntity testtest; /** * Get all Classes for testing @@ -134,6 +136,20 @@ public static void beforeAll() throws NoSuchFieldException, SecurityException, N testSimpleEntity = new TestSimpleEntity("TestSimple", 15).getClass(); // Fields fieldSimpleEntity = testSimpleEntity.getDeclaredField("simpleEntity"); + fieldSimpleEntityDefaultName = testSimpleEntity.getDeclaredField("simpleEntityDefaultName"); + fieldTestEntityExisting = testSimpleEntity.getDeclaredField("simpleEntityDefaultName"); + } + + @Test + public void testIsFieldEntitySuccess() { + + assertThat(new SQLUtil().isFieldEntity(fieldTestEntityExisting)).isTrue(); + } + + @Test + public void testIsFieldEntityFail() { + + assertThat(new SQLUtil().isFieldEntity(fieldTestSizeMissing)).isFalse(); } /** @@ -700,15 +716,43 @@ public void testGetPrimaryKeyStatementNameNull() { } - /** - * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values even if the name was - * not provided. - * - */ - @Test - public void testgetForeignKeyNameWithAnnotations() { - - assertThat(new SQLUtil().getForeignKeyName(fieldSimpleEntity, "test")).isEqualTo(""); - - } + // /** + // * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values even if the name + // was + // * not provided. + // * + // */ + // @Test + // public void testgetForeignKeyNameWithAnnotationsSuccess() { + // + // assertThat(new SQLUtil().getForeignKeyName(fieldSimpleEntity, "test")).isEqualTo("simpleEntityId"); + // + // } + // + // /** + // * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values even if the name + // was + // * not provided. + // * + // */ + // @Test + // public void testgetForeignKeyNameWithAnnotationsSuccessWithDefaultName() { + // + // assertThat(new SQLUtil().getForeignKeyName(fieldSimpleEntityDefaultName, "test")).isEqualTo("simpleEntityId_test"); + // + // } + // + // /** + // * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values even if the name + // was + // * not provided. + // * + // */ + // @Test + // public void testgetForeignKeyNameWithAnnotationsSuccessFallbackIsNull() { + // + // assertThat(new SQLUtil().getForeignKeyName(fieldSimpleEntityDefaultName, + // null)).isEqualTo("simpleEntityId_default"); + // + // } } \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLForeignKeyTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLForeignKeyTest.java new file mode 100644 index 0000000000..50e8381ce3 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLForeignKeyTest.java @@ -0,0 +1,9 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest; + +/** + * TODO leholm This type ... + * + */ +public class SQLForeignKeyTest { + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java index 6f4046edbd..8737921b1c 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java @@ -3,7 +3,7 @@ import javax.persistence.Column; import javax.persistence.Id; -import com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest; +import com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest.SQLAnnotationTest; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestAnotherSimpleEntity; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestNotSoSimpleEntity; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestSimpleEntity; @@ -12,7 +12,7 @@ import jakarta.validation.constraints.Size; /** - * This class is a test class for {@link SQLUtilTest} + * This class is a test class for {@link SQLAnnotationTest} * */ public class TestSqlTypeAnnotations { diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java index 8b14a5180c..495bdbb2f9 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java @@ -33,6 +33,10 @@ public class TestSimpleEntity { @JoinColumn(name = "simpleEntityId") private TestAnotherSimpleEntity simpleEntity; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn + private TestAnotherSimpleEntity simpleEntityDefaultName; + /** * The constructor. * From 08327c6a8c558277e3f4178f4204e0dea698d473 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Fri, 28 Oct 2022 07:59:43 +0200 Subject: [PATCH 23/39] comments nad pulled upstream master --- .../cobigen/templates/devon4j/utils/SQLUtil.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index d3f4c1071d..1aca0f1ee9 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -42,8 +42,11 @@ public SQLUtil() { } /** - * @param field - * @return + * This method checks whether a declared class of the current field is annotated with the + * {@linkplain javax.persistence.Entity @Entity} Annotation. + * + * @param field {@link Field} of the pojo class + * @return true when entity */ public Boolean isFieldEntity(Field field) { @@ -60,10 +63,11 @@ public Boolean isFieldEntity(Field field) { } /** - * Returns the name of a java type as a {@link String} + * Returns the name of a java type as a {@link String}. This simplifies strings like "java.lang.Integer" to "Integer". + * When "byte" is passed as an argument "byte" will be returned. * - * @param field - * @return + * @param field {@link Field} of the pojo class + * @return Java type as {@link String} */ private String getSimpleJavaTypeString(Field field) { From 03dc464e65fb1b01feb618e808d879d09de5e30d Mon Sep 17 00:00:00 2001 From: Lurian Date: Mon, 7 Nov 2022 11:19:41 +0100 Subject: [PATCH 24/39] - Added JavaPlugin dependency to template project for testing purposes - Implemented abstract freemarker test for java class inputs - Implemented barebone SQL generation test - Rework of SQLUtil and Entity.sql template --- cobigen-templates/templates-devon4j/pom.xml | 10 + .../templates/devon4j/utils/SQLUtil.java | 655 +-------------- .../main/templates/sql_java_app/templates.xml | 4 +- ...eate_${variables.entityName}Entity.sql.ftl | 106 +-- .../templates/AbstractJavaTemplateTest.java | 116 +++ .../templates/SQLTemplateGenerationTest.java | 16 + .../templates/testclasses/SQLTestEntity.java | 20 + .../devon4j/test/utils/SQLUtilTest.java | 5 + .../utils/SQLUtilTest/SQLAnnotationTest.java | 758 ------------------ .../utils/SQLUtilTest/SQLForeignKeyTest.java | 9 - .../sqltest/TestSqlTypeAnnotations.java | 1 - 11 files changed, 224 insertions(+), 1476 deletions(-) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLAnnotationTest.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLForeignKeyTest.java diff --git a/cobigen-templates/templates-devon4j/pom.xml b/cobigen-templates/templates-devon4j/pom.xml index c20e74ecb4..e2ff65a882 100644 --- a/cobigen-templates/templates-devon4j/pom.xml +++ b/cobigen-templates/templates-devon4j/pom.xml @@ -59,6 +59,16 @@ jakarta.validation-api 3.0.2 + + com.devonfw.cobigen + javaplugin + test + + + com.devonfw.cobigen + tempeng-freemarker + test + diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 1aca0f1ee9..b189f8ea37 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -1,31 +1,6 @@ package com.devonfw.cobigen.templates.devon4j.utils; -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; -import javax.persistence.ManyToOne; -import javax.persistence.OneToOne; -import javax.persistence.Table; - -import org.apache.commons.lang3.StringUtils; - -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; +import java.util.*; /** * Provides operations to identify and process SQL specific information @@ -42,629 +17,67 @@ public SQLUtil() { } /** - * This method checks whether a declared class of the current field is annotated with the - * {@linkplain javax.persistence.Entity @Entity} Annotation. - * - * @param field {@link Field} of the pojo class - * @return true when entity - */ - public Boolean isFieldEntity(Field field) { - - try { - - Class clazz = Class.forName(field.getType().getCanonicalName()); - - return clazz.isAnnotationPresent(Entity.class); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException( - "The declared class of this field type can not be verified as an Entity because the java class does not exist!", - e); - } - } - - /** - * Returns the name of a java type as a {@link String}. This simplifies strings like "java.lang.Integer" to "Integer". - * When "byte" is passed as an argument "byte" will be returned. - * - * @param field {@link Field} of the pojo class - * @return Java type as {@link String} - */ - private String getSimpleJavaTypeString(Field field) { - - // Either a full qualified class name or a simple string like "byte" for primitives - String canonicalTypeName = field.getType().getCanonicalName(); - boolean isExistingClass = false; - - // Verifies that the provided class exists if it's not a primitive - if (!field.getType().isPrimitive()) { - try { - Class.forName(canonicalTypeName); - isExistingClass = true; - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException( - "The provided type can not be converted into an SQL type equivalent because the java class doesn't exist!", - e); - } - } - - // Get the class name from *.Byte or Byte - Pattern myClassPattern = Pattern.compile("(\\.|^)((?:.(?!\\.))+)$"); - Matcher myClassMatch = myClassPattern.matcher(canonicalTypeName); - - if (field.getType().isPrimitive() || isExistingClass) { - if (myClassMatch.find()) { - return myClassMatch.group(2); - - } - - } - throw new IllegalAccessError( - "The provided field is neither an existing class nor an existing primitive type. Cannot generate template as it might obviously depend on reflection."); - } - - /** - * Helper function to map a Java Type to its equivalent SQL type - * - * @param field {@link Field} of the pojo class - * @return returns the equivalent SQL type - * @throws IllegalArgumentException when type is not a java type - * @throws IllegalAccessError when the provided field wa - */ - private String mapJavaToSqlType(Field field) throws IllegalArgumentException, IllegalAccessError { - - String javaType = getSimpleJavaTypeString(field); - - if (isEnum(field.getType().getCanonicalName())) { - return "INTEGER"; - } - - switch (javaType) { - // INTEGER - case "Integer": - return "INTEGER"; - case "int": - return "INTEGER"; - case "Year": - return "INTEGER"; - case "Month": - return "INTEGER"; - // BIGINT - case "Long": - return "BIGINT"; - case "long": - return "BIGINT"; - case "Object": - return "BIGINT"; - // SMALLINT - case "Short": - return "SMALLINT"; - case "short": - return "SMALLINT"; - // FLOAT - case "Float": - return "FLOAT"; - case "float": - return "FLOAT"; - // DOUBLE - case "Double": - return "DOUBLE"; - case "double": - return "DOUBLE"; - // NUMERIC - case "BigDecimal": - return "NUMERIC"; - case "BigInteger": - return "NUMERIC"; - // CHAR - case "Character": - return "CHAR"; - case "char": - return "CHAR"; - // TINYINT - case "Byte": - return "TINYINT"; - case "byte": - return "TINYINT"; - // BOOLEAN - case "Boolean": - return "BOOLEAN"; - case "boolean": - return "BOOLEAN"; - // TIMESTAMP - case "Instant": - return "TIMESTAMP"; - case "Timestamp": - return "TIMESTAMP"; - // DATE - case "Date": - return "DATE"; - case "Calendar": - return "DATE"; - // TIME - case "Time": - return "TIME"; - // BINARY - case "UUID": - return "BINARY"; - // BLOB - case "Blob": - return "BLOB"; - // String, Entities - default: - return "VARCHAR"; - } - } - - /** - * Get a basic SQL type statement for wrapper and primitives. - * - * @param field {@link Field} of the pojo class - * @return SQL statement as {@link String} - */ - public String getSimpleSQLtype(Field field) { - - String sqlStatement = ""; - String type = mapJavaToSqlType(field); - sqlStatement += type; - - if (type.contains("VARCHAR") && field.isAnnotationPresent(Size.class)) { - Integer maxSize = field.getAnnotation(Size.class).max(); - sqlStatement = sqlStatement + "(" + maxSize.toString() + ")"; - } - - if (field.isAnnotationPresent(Column.class) && field.getAnnotation(Column.class).nullable() - || field.isAnnotationPresent(NotNull.class)) { - sqlStatement = sqlStatement + " NOT NULL"; - } - - return sqlStatement; - } - - /** - * Helper methods to get all fields recursively including fields from super classes - * - * @param fields list of fields to be accumulated during recursion - * @param cl class to find declared fields - * @return list of all fields - */ - private static List getAllFields(List fields, Class cl) { - - fields.addAll(Arrays.asList(cl.getDeclaredFields())); - - if (cl.getSuperclass() != null) { - getAllFields(fields, cl.getSuperclass()); - } - - return fields; - } - - /** - * Helper method to get a field of a pojo class by its name. Including fields from super classes - * - * @param pojoClass {@link Class} the class object of the pojo - * @param fieldName {@link String} the name of the field - * @return return the field object throws IllegalArgumentException - * @throws IllegalAccessError when the pojoClass is null - */ - public Field getFieldByName(Class pojoClass, String fieldName) throws IllegalAccessError { - - if (pojoClass != null) { - // automatically fetches all fields from pojoClass including its super classes - List fields = new ArrayList<>(); - getAllFields(fields, pojoClass); - - Optional field = fields.stream().filter(f -> f.getName().equals(fieldName)).findFirst(); - - if (field.isPresent()) { - return field.get(); - } else { - throw new IllegalAccessError( - "This field doesn't exist. Cannot generate template as it might obviously depend on reflection."); - } - } - - throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); - } - - /** - * Method to retrieve the annotations of a field - * - * @param pojoClass {@link Class} the class object of the pojo - * @param fieldName {@link String} the name of the field - * @return an array with annotations found (length = 0 if now annotations were found) - */ - public Annotation[] getFieldAnnotations(Class pojoClass, String fieldName) { - - if (fieldName.isBlank() || fieldName == null) { - throw new IllegalAccessError( - "It is not possible to look for a non existing field. Cannot generate template as it might obviously depend on reflection."); - } - - if (pojoClass != null) { - Annotation[] annotations; - Field field = getFieldByName(pojoClass, fieldName); - annotations = field.getAnnotations(); - return annotations; - } - throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); - } - - /** - * Get the annotated table name of a given Entity class - * - * @param field {@link Field} of the pojo class - * @return return the annotated table name if - * @throws ClassNotFoundException to Log an error - */ - public String getEntityTableName(Field field) throws ClassNotFoundException { - - if (field == null) { - throw new IllegalAccessError( - "Field object is null. Cannot generate template as it might obviously depend on reflection."); - } - if (!field.getType().getCanonicalName().endsWith("Entity")) { - throw new IllegalAccessError("The field " + field.getName() - + " is not an entity class. Cannot generate template as it might obviously depend on reflection."); - } - - try { - - Class entityClass = Class.forName(field.getType().getCanonicalName()); - Table table = entityClass.getAnnotation(Table.class); - - String javaType = getSimpleJavaTypeString(field); - - if (table == null) { - return StringUtils.left(javaType, javaType.length() - "Entity".length()); - } else { - return table.name().isEmpty() ? StringUtils.left(javaType, javaType.length() - "Entity".length()) - : table.name(); - } - - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException( - "Class path doesn't exist. Cannot generate template as it might obviously depend on reflection.", e); - } - - } - - /** - * Returns the name of a field when the @Column annotation sets the name option that name will be used. - * - * @param field the current pojo field - * @return {@link String} + * Debug function to set breakpoint to analyze some data passed to the freemarkertemplate */ - public String getColumnName(Field field) { + public void debug(Map pojo) { - if (field == null) { - throw new IllegalAccessError( - "Field object is null. Cannot generate template as it might obviously depend on reflection."); - } - return field.isAnnotationPresent(Column.class) && !field.getAnnotation(Column.class).name().isBlank() - ? field.getAnnotation(Column.class).name() - : field.getName(); + System.out.println("DEBUG"); } - /** - * Returns the name of a method when the @Column annotation sets the name option that name will be used. - * - * @param method the current method - * @return {@link String} - */ - public String getColumnName(Method method) { + public String tableName(String entityName) { - if (method == null) { - throw new IllegalAccessError( - "Method object is null. Cannot generate template as it might obviously depend on reflection."); - } - return method.isAnnotationPresent(Column.class) && !method.getAnnotation(Column.class).name().isBlank() - ? method.getAnnotation(Column.class).name() - : method.getName(); + return entityName.replace("Entity", "").toUpperCase(); } - /** - * Get the primary key and its type of a given Entity class - * - * @param className {@link String} full qualified class name - * @return the primary key and its type if found or null - */ - public String getPrimaryKey(String className) { - - if (className == null) { - throw new IllegalAccessError( - "The provides class name can not be null. Cannot generate template as it might obviously depend on reflection."); - } - try { - Class entityClass = Class.forName(className); - List fields = new ArrayList<>(); - getAllFields(fields, entityClass); - for (Field field : fields) { - - if (field.isAnnotationPresent(Id.class)) { - - // return canonical field name , column name - return field.getType().getCanonicalName() + "," + getColumnName(field); - } else { - Optional getterOptional = Arrays.stream(entityClass.getMethods()) - .filter(m -> m.getName() - .equals("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1)) - && m.isAnnotationPresent(Id.class)) - .findFirst(); - if (getterOptional.isPresent()) { - - Method getter = getterOptional.get(); - - return getter.getReturnType().getCanonicalName() + "," + getColumnName(getter); - } - } - } - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException( - "Class path doesn't exist. Cannot generate template as it might obviously depend on reflection.", e); - } + public String primaryKeyStatement(Map field) { - throw new IllegalAccessError( - "Could not find the field or getter with @Id annotated. Cannot generate template as it might obviously depend on reflection."); + String fieldName; + Map annotations = getValue(field, "annotations"); + fieldName = getValue(field, "name"); + String incrementType = "AUTO_INCREMENT"; + return String.format("%s BIGINT %s PRIMARY KEY", fieldName, incrementType); } - /** - * Get a List of HashMap Keys holding information about a given field. Default value overloaded by - * {@link SQLUtil#getPrimaryKeyStatement(Field, String)} - * - * @param field the current pojo field - * @return HashMap containing name and type of a column - */ - public HashMap getPrimaryKeyStatement(Field field) { + public String foreignKeyStatement(Map field) { - if (field == null) { - throw new IllegalAccessError( - "Field object is null. Cannot generate template as it might obviously depend on reflection."); - } - String name = getColumnName(field); - HashMap column = getPrimaryKeyStatement(field, name); - return column; + return ""; } /** - * Get a List of HashMap Keys holding information about a given field - * - * @param field the current pojo field - * @param name the column name related to that field - * @return HashMap containing name and type of a column + * Extracts the name of the field from the Map whilst checking for name-overriding annotations. */ - public HashMap getPrimaryKeyStatement(Field field, String name) { - - if (field == null) { - throw new IllegalAccessError( - "Field object is null. Cannot generate template as it might obviously depend on reflection."); + static private String getFieldName(Map field) { + String fieldName = chainAccess(field, new String[] { "annotations", "javax_persistence_Column", "name" }); + if (fieldName != null) { + return fieldName; + } else { + return getValue(field, "name"); } - - if (name == null || name.isBlank()) { - name = getColumnName(field); - } - String type = getSimpleSQLtype(field); - - if (!type.contains("NOT NULL")) { - // Make sure Primary Keys will always be created with the NOT NULL statement - type = type + " NOT NULL"; - } - - if (field.isAnnotationPresent(GeneratedValue.class) - && field.getAnnotation(GeneratedValue.class).strategy().equals(GenerationType.AUTO)) { - type = type + " AUTO INCREMENT"; - } - - HashMap column = columnMapBuild(name, type); - - return column; } /** - * Helper method to build a hash map for foreign key value pairs - * - * @param name name of the column - * @param type type of the column - * @return HashMap containing name and type of a column + * Dynamic Helper function to navigate nested maps. Returns null on any type of error */ - private HashMap columnMapBuild(String name, String type) { - - HashMap columnMap = new HashMap() { - { - put("name", name); - put("type", type); - } - }; - - return columnMap; - } - - /** - * Helper method to build a hash map for foreign key value pairs - * - * @param name name of the foreign key - * @param table table the current table name - * @param columnname referenced column name - * @return HashMap for name, table and column name - */ - private HashMap fkMapBuild(String name, String table, String columnname) { - - HashMap foreignKeyMap = new HashMap() { - { - put("name", name); - put("table", table); - put("columnname", columnname); - } - }; - return foreignKeyMap; - } - - /** - * Get the name of the Column for the Foreign Key. Extracted from - * {@linkplain javax.persistence.JoinColumn#name() @JoinColumn.name()}. When no name has been provided in the - * Annotation a string with a fallback name will be generated. - * - * @param field current pojo class - * @param fallBack String for fallback option - * @return {@link String} Column name for Foreign Key declaration - */ - public String getForeignKeyColumnName(Field field, String fallBack) { - - if (field == null) { - throw new IllegalAccessError("Field object is null. Cannot generate a column name for a foreign key."); - } - - if (field.isAnnotationPresent(JoinColumn.class)) { - - if (fallBack == null || fallBack.isBlank()) { - fallBack = "FK"; - } - // A name will be generated based on the option set in @JoinColumn.name() - return !field.getAnnotation(JoinColumn.class).name().isBlank() ? field.getAnnotation(JoinColumn.class).name() - : fallBack + "_" + field.getName(); - } - throw new IllegalAccessError("Field is not annotated with @JoinColumn. Implicit foreign keys are not allowed."); - } - - /** - * Retrieve the name of a referenced column name and its type statement based on the provided field. @JoinColumn has - * precedence over @OneToMany when the field is annotated with both the option referencedColumnName from - * the @JoinColumn annotation and the mappedBy option from the @OneToMany annotation. The method will find the owner - * of the entity if none of the options are provided. This method will mostly be used on Entity types. - * - * @param field current pojo class - * @return comma separated String or null - */ - public String getForeignKeyStatement(Field field) { - - String foreignKeyOwnedBy = null, type = ""; - + static private T chainAccess(Map map, String[] nestedFields) { try { - // The current field type is the class in which we are searching the primary key - Class foreignEntityClass = Class.forName(field.getType().getCanonicalName()); - - // Building the column name based on provided annotations - if (field.isAnnotationPresent(JoinColumn.class) - && !field.getAnnotation(JoinColumn.class).referencedColumnName().isBlank()) { - // Annotation @JoinColumn is set and a name was provided - foreignKeyOwnedBy = field.getAnnotation(JoinColumn.class).referencedColumnName(); - - } else if (field.isAnnotationPresent(OneToOne.class) - && !field.getAnnotation(OneToOne.class).mappedBy().isBlank()) { - // mappedBy option is set by @OneToOne annotation - foreignKeyOwnedBy = field.getAnnotation(OneToOne.class).mappedBy(); - } else if (field.isAnnotationPresent(JoinColumns.class)) { - - JoinColumn[] joinColumnDeclarations = field.getAnnotation(JoinColumns.class).value(); - // @JoinColumns holds multiple @JoinColumn - // for (JoinColumn joinColumn : joinColumnDeclarations) { - // - // } - - field.getAnnotation(JoinColumns.class).value(); - } else if (field.isAnnotationPresent(JoinColumn.class)) { - // Default values to JoinColumn apply - String[] pkReceived = getPrimaryKey(field.getType().getCanonicalName()).split(","); - foreignKeyOwnedBy = pkReceived[1]; - + for (int i = 0; i < nestedFields.length - 1; i++) { + String key = nestedFields[i]; + map = getValue(map, key); } - - try { - // Retrieving the field for the calculated coumnName - Field foreignField = foreignEntityClass.getDeclaredField(foreignKeyOwnedBy); - // Field was found and its type will be mapped into the SQL world - type = getSimpleSQLtype(foreignField); - } catch (NoSuchFieldException e) { - throw new IllegalAccessError( - "Couldn't find the foreign key field. Cannot generate template as it might obviously depend on reflection."); - } - - // Returning comma separated string so that it can be used in freemarker template with the split method - return foreignKeyOwnedBy + "," + type; - - } catch (ClassNotFoundException e) { - throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); - } - } - - /** - * Get HashMap of Key and Value pairs holding the information about foreign keys. It will be assumed that the current - * field is an entity. This function defaults the {@link String} name parameter of - * {@link SQLUtil#getForeignKeyData(Field, String)} - * - * @param field the pojo field - * @return HashMap holding the information {"name": name, "table": table, "columnname": columnname} - * - * @name {@link String} the name of the foreign key - * @table {@link String} the table which is referenced by the foreign key - * @columnname {@link String} the column name of the referenced with @id annotated variable - */ - public HashMap getForeignKeyData(Field field) { - - // Assumes @JoinColumn is present - if (field.isAnnotationPresent(JoinColumn.class)) { - String[] fkDeclaration = getForeignKeyStatement(field).split(","); - String name = getForeignKeyColumnName(field, fkDeclaration[0]); - - return getForeignKeyData(field, name); + return getValue(map, nestedFields[nestedFields.length - 1]); + } catch (Exception ignored) { + return null; } - - throw new IllegalAccessError( - "The ForeignKeyData couldn't be generated. Cannot generate template as it might obviously depend on reflection."); } /** - * Get a List of Key and Value pairs holding the information about foreign keys. It will be assumed that the current - * field is an entity. - * - * @param field the pojo field - * @param name the name of the foreign key - * @return HashMap holding the information {"name": name, "table": table, "columnname": columnname} or null - * - * @name {@link String} the name of the foreign key - * @table {@link String} the table which is referenced by the foreign key - * @columnname {@link String} the column name of the referenced with @id annotated variable + * Parametrized helper function to dynamically extract data from a map. Returns null on casting errors */ - public HashMap getForeignKeyData(Field field, String name) { - - // Assumes @JoinColumn is present - if (field.isAnnotationPresent(JoinColumn.class)) { - String[] fkDeclaration = getForeignKeyStatement(field).split(","); - String tableName = getForeignKeyTableName(field); - HashMap foreignKeyData = fkMapBuild(name, tableName, fkDeclaration[0]); - - return foreignKeyData; - } - - throw new IllegalAccessError( - "The ForeignKeyData couldn't be generated. Cannot generate template as it might obviously depend on reflection."); - } - - /** - * Get the table for the foreign key that the current field was annotated with - * - * @param field current pojo class - * @return table name as a {@link String} or null - */ - public String getForeignKeyTableName(Field field) { - + static private T getValue(Map map, String key) { try { - String tableName; - if (field.isAnnotationPresent(ManyToOne.class) || field.isAnnotationPresent(OneToOne.class)) { - tableName = getEntityTableName(field); - } else { - tableName = getSimpleJavaTypeString(field); - } - - return tableName; - } catch (ClassNotFoundException e) { - throw new IllegalAccessError( - "It is not possible to look for a non existing field. Cannot generate template as it might obviously depend on reflection."); - + return (T) map.get(key); + } catch (Exception ignored) { + return null; } - } - } diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml index 44e8feb936..3e3f4fca76 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="4.0"> - + @@ -13,7 +13,7 @@ - + diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl index 4c6588413b..e9a71a0903 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -1,93 +1,29 @@ -<#assign tableName = JavaUtil.getEntityTableName(pojo.canonicalName)> +${SQLUtil.debug(pojo)} +<#if pojo.annotations.javax_persistence_Table??> + <#assign tableName = pojo.annotations.javax_persistence_Table.name> +<#else> + <#assign tableName = SQLUtil.tableName(pojo.name)> + +<#assign statements = []> +<#assign joinTables = []> + CREATE TABLE ${tableName} ( -<#assign fkList = []> -<#assign columns = []> -<#assign refTables = []> <#list pojo.methodAccessibleFields as field> - <#if !field.annotations.javax_persistence_Transient??> - <#assign name = SQLUtil.getColumnName(field)> - <#--Field: primary key--> - <#if field.annotations.javax_persistence_Id??> - <#assign pk = name> - <#assign columns = columns + [SQLUtil.getPrimaryKeySQLstatement(field, name)]> - <#elseif !SQLUtil.isCollection(classObject, field.name)> - <#--Field: simple entity--> - <#if field.type?ends_with("Entity")> - <#assign name = SQLUtil.getForeignKeyName(field)> - <#assign type = SQLUtil.getForeignKeyDeclaration(field, name)?split(",")[1]> - <#assign fkList = fkList + [SQLUtil.getForeignKeyData(field, name)]> - <#else> - <#--Field: primitive--> - <#assign type = SQLUtil.getSimpleSQLtype(field)> - - <#assign columns = columns + [{"name": name, "type":type}]> - <#else> - <#if field.annotations.javax_persistence_ManyToMany?? && field.annotations.javax_persistence_JoinTable??> - <#--Field: collection of entity--> - <#assign entity = field.canonicalType?substring(field.canonicalType?index_of("<") + 1,field.canonicalType?length - 1)> - <#assign entityTable = JavaUtil.getEntityTableName(entity)> - <#assign table1 = tableName> - <#assign table2 = entityTable> - <#if field.annotations.javax_persistence_JoinTable.name?has_content> - <#assign refTableName = field.annotations.javax_persistence_JoinTable.name> - <#else> - <#assign refTableName = table1 + "_" + table2> - - <#--not yet support multiple JoinColumns or no JoinColumn--> - <#if field.annotations.javax_persistence_JoinTable.joinColumns?has_content - && field.annotations.javax_persistence_JoinTable.joinColumns?is_enumerable - && field.annotations.javax_persistence_JoinTable.joinColumns[0]?has_content> - <#assign col = field.annotations.javax_persistence_JoinTable.joinColumns[0].javax_persistence_JoinColumn> - <#assign name1 = col.name> - <#if col.referencedColumnName?has_content> - <#assign id1 = col.referencedColumnName> - <#assign type1 = get_mapping_type(JavaUtil.getCanonicalNameOfField(pojo.canonicalName, id1))> - <#else> - <#assign result = JavaUtil.getPrimaryKey(pojo.canonicalName)?split(",")> - <#assign type1 = get_mapping_type(result[0])> - <#assign id1 = result[1]> - - <#else> - <#continue> - - <#if field.annotations.javax_persistence_JoinTable.inverseJoinColumns?has_content - && field.annotations.javax_persistence_JoinTable.inverseJoinColumns?is_enumerable - && field.annotations.javax_persistence_JoinTable.inverseJoinColumns[0]?has_content> - <#assign col = field.annotations.javax_persistence_JoinTable.inverseJoinColumns[0].javax_persistence_JoinColumn> - <#assign name2 = col.name> - <#if col.referencedColumnName?has_content> - <#assign id2 = col.referencedColumnName> - <#assign type2 = get_mapping_type(JavaUtil.getCanonicalNameOfField(entity, id2))> - <#else> - <#assign result = JavaUtil.getPrimaryKey(entity)?split(",")> - <#assign type2 = get_mapping_type(result[0])> - <#assign id2 = result[1]> - - <#else> - <#continue> - - <#assign refTables = refTables + [{"table": refTableName, "columns":[{"name": name1, "id": id1, "type": type1, "table": table1}, {"name": name2, "id": id2, "type": type2, "table": table2}] }]> - - + <#-- Skip Transient fields --> + <#if field.annotations.javax_persistence_Transient??> + <#continue> + + <#-- Primary Key statement --> + <#if field.annotations.javax_persistence_Id??> + <#assign statements += [SQLUtil.primaryKeyStatement(field)]> + <#elseif field.annotations.javax_persistence_JoinColumn> -<#list columns as col> - ${col.name?right_pad(30)} ${col.type}, - - CONSTRAINT PK_${tableName} PRIMARY KEY(${pk}), -<#list fkList as fk> - CONSTRAINT FK_${tableName}_${fk.key} FOREIGN KEY(${fk.key}) REFERENCES ${fk.table}(${fk.id}), +<#list statements as statement> + ${statement}, ); -<#list refTables as tb> -CREATE TABLE ${tb.table} ( -<#assign col1 = tb.columns[0]> -<#assign col2 = tb.columns[1]> - ${col1.name?right_pad(30)} ${col1.type} NOT NULL, - ${col2.name?right_pad(30)} ${col2.type} NOT NULL, - CONSTRAINT PK_${tb.table} PRIMARY KEY(${col1.name}, ${col2.name}), - CONSTRAINT FK_${tb.table}_${col1.name} FOREIGN KEY(${col1.name}) REFERENCES ${col1.table}(${col1.id}), - CONSTRAINT FK_${tb.table}_${col2.name} FOREIGN KEY(${col2.name}) REFERENCES ${col2.table}(${col2.id}), -); +<#list joinTables as tb> + \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java new file mode 100644 index 0000000000..0d93bcd715 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java @@ -0,0 +1,116 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates; + +import com.devonfw.cobigen.api.extension.TextTemplate; +import com.devonfw.cobigen.javaplugin.inputreader.JavaInputReader; +import com.devonfw.cobigen.tempeng.freemarker.FreeMarkerTemplateEngine; +import org.junit.Before; + +import java.io.StringWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +public abstract class AbstractJavaTemplateTest { + /** + * Model used by freemarker + */ + public Map model; + public FreeMarkerTemplateEngine engine; + /** + * Implementation of TextTemplate for template processing + */ + public TextTemplate template; + + /** + * Creates an anonymous TextTemplate object that works on the given template files + * + * @param relativePath Relative Path to the template from the /templates folder, most likely coincides with template + * filename + * @param relativeAbsolutePath Relative path from source root to the template + */ + public TextTemplate createTemplate(String relativePath, Path relativeAbsolutePath) { + + return new TextTemplate() { + @Override + public String getRelativeTemplatePath() { + + return relativePath; + } + + @Override + public Path getAbsoluteTemplatePath() { + + return relativeAbsolutePath; + } + }; + } + + /** + * Creates a template engine for the given template path + * + * @param templateFolderPath Relative path to template folder from project source root + */ + public FreeMarkerTemplateEngine createEngine(String templateFolderPath) { + + FreeMarkerTemplateEngine engine = new FreeMarkerTemplateEngine(); + engine.setTemplateFolder(Paths.get(templateFolderPath)); + return engine; + } + + /** + * Instanciates a class of the given Type and adds it to the model with it's simplename. + * (Just like all Utils following the naming convention) + * @param clazz Class with a public NoArgsConstructor to instanciate it + */ + public void addUtil(Class clazz) { + try { + String name = clazz.getSimpleName(); + Object instance = clazz.getConstructor().newInstance(); + model.put(name, instance); + } catch (Exception exception) { + exception.printStackTrace(); + throw new RuntimeException("Failed adding the Util to the Model, please check the error stacktrace and fix it or add the util manually!"); + } + + } + + /** + * Adds object to the model + * @param key + * @param instance + */ + public void addObject(String key, Object instance) { + this.model.put(key, instance); + } + + /** + * Consumes current test configuration to produce an output! + * @return Produced file + */ + public String process() { + StringWriter out = new StringWriter(); + engine.process(template, model, out, "UTF-8"); + return out.toString(); + } + + /** + * Auto initialization for test. For more fine-grained control initialize your own configuration with the + * helper methods. + * @param templatePath Path to the template relative to subproject source root + * @param utils Classes to auto-instanciate and inject into freemarker for the template + * @param modelClass Class to auto-generate reflective pojo model + */ + public void defaultInit(String templatePath, Class modelClass, Class[] utils) { + Path tp = Paths.get(templatePath); + String filename = tp.getFileName().toString(); + Path templateFolder = tp.getParent(); + model = new JavaInputReader().createModel(modelClass); + template = createTemplate(filename, templateFolder); + engine = new FreeMarkerTemplateEngine(); + engine.setTemplateFolder(templateFolder); + for (Class utilClass : utils) { + addUtil(utilClass); + } + } +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java new file mode 100644 index 0000000000..afdc0b11c9 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java @@ -0,0 +1,16 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates; + +import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntity; +import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; +import org.junit.Test; + +public class SQLTemplateGenerationTest extends AbstractJavaTemplateTest { + + @Test + public void generateSQLTest() { + + this.defaultInit("src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl", + SQLTestEntity.class, new Class[] { SQLUtil.class }); + String output = this.process(); + } +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java new file mode 100644 index 0000000000..c3c2dbd91f --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java @@ -0,0 +1,20 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "SQLTEST") +public class SQLTestEntity { + @Id + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java new file mode 100644 index 0000000000..1962ac57e4 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -0,0 +1,5 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils; + +public class SQLUtilTest { + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLAnnotationTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLAnnotationTest.java deleted file mode 100644 index 0238c4976f..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLAnnotationTest.java +++ /dev/null @@ -1,758 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertThrows; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.HashMap; - -import org.assertj.core.api.Condition; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestCat; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.TestSqlTypeAnnotations; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestAnotherSimpleEntity; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestNotSoSimpleEntity; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestSimpleEntity; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryfive; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryfour; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryone; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primarythree; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primarytwo; -import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; - -/** - * Test class for {@link SQLUtil} - */ -public class SQLAnnotationTest { - - // Annotation test class - private static Class testSqlTypeAnnotations; - - // Annotation test fields - private static Field fieldTestSimpleString, fieldTestAtSize, fieldTestSizeMissing, fieldTestSimpleInteger, - fieldTestAtColumnNullableAtNotNull, fieldTestAtColumnNullable, fieldTestAtColumnNotNullableAtNotNull, - fieldTestAtColumnNotNullable, fieldTestAtNotNull, fieldTestAtSizeAtNotNull, - fieldTestEntityAtColumnNotNullableAtSizeAtNotNull, fieldTestEntityAtTable, fieldTestAnonymousClassEntity, - fieldTestEntityAtTableNameDefault, fieldTestAnonymousEntityAtTableNameDefault, fieldTestEntityAtTableNull, - fieldTestAnonymousEntityAtTableNull; - - // Class and Field lookup test class - private static Class testTestCatClass; - - // Field test fields for lookup comparison - private static Field testResultLegs; - - // Class for primary keys - private static Class testPrimaryOne, testPrimaryTwo, testPrimaryThree, testPrimaryFour, testPrimaryFive; - - // Field for primary keys - private static Field fieldTestAtId, fieldTestAtIdAtGeneratedValue; - - // Simple fields - private static Field fieldGetColumnNameFieldAtColumn, fieldGetColumnNameFieldAtColumnBlank, - fieldGetColumnNameFieldAtColumnMissing; - - // Simple methods - private static Method methodGetColumnNameMethodAtColumn, methodGetColumnNameMethodAtColumnBlank, - methodGetColumnNameMethodAtColumnMissing; - - // Foreign Keys - // Classes - private static Class testAnotherSimpleEntity, testNotSoSimpleEntity, testSimpleEntity; - - // Fields - private static Field fieldSimpleEntity, fieldSimpleEntityDefaultName, fieldTestEntityExisting; - - private static TestSimpleEntity testtest; - - /** - * Get all Classes for testing - * - * @throws SecurityException - * @throws NoSuchFieldException - * @throws NoSuchMethodException - */ - @SuppressWarnings("javadoc") - @BeforeClass - public static void beforeAll() throws NoSuchFieldException, SecurityException, NoSuchMethodException { - - // Annotation class - testSqlTypeAnnotations = new TestSqlTypeAnnotations().getClass(); - // Annotation fields - fieldTestSimpleString = testSqlTypeAnnotations.getDeclaredField("testSimpleString"); - fieldTestAtSize = testSqlTypeAnnotations.getDeclaredField("testAtSize"); - fieldTestSizeMissing = testSqlTypeAnnotations.getDeclaredField("testSizeMissing"); - fieldTestSimpleInteger = testSqlTypeAnnotations.getDeclaredField("testSimpleInteger"); - fieldTestAtColumnNullableAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtColumnNullableAtNotNull"); - fieldTestAtColumnNullable = testSqlTypeAnnotations.getDeclaredField("testAtColumnNullable"); - fieldTestAtColumnNotNullableAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtColumnNotNullableAtNotNull"); - fieldTestAtColumnNotNullable = testSqlTypeAnnotations.getDeclaredField("testAtColumnNotNullable"); - fieldTestAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtNotNull"); - fieldTestAtSizeAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtSizeAtNotNull"); - fieldTestAtSizeAtNotNull = testSqlTypeAnnotations.getDeclaredField("testAtSizeAtNotNull"); - fieldTestEntityAtColumnNotNullableAtSizeAtNotNull = testSqlTypeAnnotations - .getDeclaredField("testEntityAtColumnNotNullableAtSizeAtNotNull"); - fieldTestEntityAtTable = testSqlTypeAnnotations.getDeclaredField("testEntityAtTable"); - fieldTestAnonymousClassEntity = testSqlTypeAnnotations.getDeclaredField("testAnonymousEntityAtTable"); - fieldTestEntityAtTableNameDefault = testSqlTypeAnnotations.getDeclaredField("testEntityAtTableNameDefault"); - fieldTestAnonymousEntityAtTableNameDefault = testSqlTypeAnnotations - .getDeclaredField("testAnonymousEntityAtTableNameDefault"); - fieldTestEntityAtTableNull = testSqlTypeAnnotations.getDeclaredField("testEntityAtTableNull"); - fieldTestAnonymousEntityAtTableNull = testSqlTypeAnnotations.getDeclaredField("testAnonymousEntityAtTableNull"); - fieldGetColumnNameFieldAtColumn = testSqlTypeAnnotations.getDeclaredField("testGetColumnNameFieldAtColumn"); - fieldGetColumnNameFieldAtColumnBlank = testSqlTypeAnnotations - .getDeclaredField("testGetColumnNameFieldAtColumnBlank"); - fieldGetColumnNameFieldAtColumnMissing = testSqlTypeAnnotations - .getDeclaredField("testGetColumnNameFieldAtColumnMissing"); - methodGetColumnNameMethodAtColumn = testSqlTypeAnnotations.getDeclaredMethod("getTestGetColumnNameMethodAtColumn"); - methodGetColumnNameMethodAtColumnBlank = testSqlTypeAnnotations - .getDeclaredMethod("getTestGetColumnNameMethodAtColumnBlank"); - methodGetColumnNameMethodAtColumnMissing = testSqlTypeAnnotations - .getDeclaredMethod("getTestGetColumnNameMethodAtColumnMissing"); - // Class and Field lookup class - testTestCatClass = new TestCat("Molly", 15).getClass(); - - // Field test fields for lookup comparison - testResultLegs = testTestCatClass.getDeclaredField("legs"); - - // Annotation class primary key - testPrimaryOne = new Primaryone().getClass(); - testPrimaryTwo = new Primarytwo().getClass(); - testPrimaryThree = new Primarythree().getClass(); - testPrimaryFour = new Primaryfour().getClass(); - testPrimaryFive = new Primaryfive().getClass(); - // Annotation fields primary key - fieldTestAtId = testPrimaryOne.getDeclaredField("id"); - fieldTestAtIdAtGeneratedValue = testPrimaryTwo.getDeclaredField("id"); - - // Foreign key - // Classes - testAnotherSimpleEntity = new TestAnotherSimpleEntity("TestAnotherSimple", 20).getClass(); - testNotSoSimpleEntity = new TestNotSoSimpleEntity("TestNotSoSimple", 18).getClass(); - testSimpleEntity = new TestSimpleEntity("TestSimple", 15).getClass(); - // Fields - fieldSimpleEntity = testSimpleEntity.getDeclaredField("simpleEntity"); - fieldSimpleEntityDefaultName = testSimpleEntity.getDeclaredField("simpleEntityDefaultName"); - fieldTestEntityExisting = testSimpleEntity.getDeclaredField("simpleEntityDefaultName"); - } - - @Test - public void testIsFieldEntitySuccess() { - - assertThat(new SQLUtil().isFieldEntity(fieldTestEntityExisting)).isTrue(); - } - - @Test - public void testIsFieldEntityFail() { - - assertThat(new SQLUtil().isFieldEntity(fieldTestSizeMissing)).isFalse(); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current - * {@linkplain java.lang.reflect.Field field} is translated to the SQL Type VARCHAR. - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationForStringType() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestSimpleString)).isEqualTo("VARCHAR"); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current - * {@linkplain java.lang.reflect.Field field} is translated to the SQL Type VARCHAR(SIZE) when the - * {@linkplain java.lang.reflect.Field field} is provided with the annotation - * {@linkplain jakarta.validation.constraints.Size @Size}. - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationIsVarcharSize() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtSize)).isEqualTo("VARCHAR(2147483647)"); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current - * {@linkplain java.lang.reflect.Field field} is translated to the SQL Type INTEGER and the - * {@linkplain java.lang.reflect.Field field} is provided with the annotation - * {@linkplain jakarta.validation.constraints.Size}. - * - * This scenario shows that the generation ignores the {@linkplain jakarta.validation.constraints.Size} annotation - * when the current Java type is not a {@linkplain String}. - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationSizeIsMissing() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestSizeMissing)).isEqualTo("INTEGER"); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string when the type of the current - * {@linkplain java.lang.reflect.Field field} is translated to the SQL Type INTEGER. - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationForIntegerType() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestSimpleInteger)).isEqualTo("INTEGER"); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotations - * {@linkplain javax.persistence.Column @Column} and {@linkplain jakarta.validation.constraints.NotNull @NotNull}. - * When either {@linkplain javax.persistence.Column#nullable nullable} from - * {@linkplain javax.persistence.Column @Column} is set to {@linkplain java.lang.Boolean#TRUE true} or the in this - * case redundant annotation {@linkplain jakarta.validation.constraints.NotNull @NotNull} is provided. The return - * string should contain NOT NULL. - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationAtColumnNullableAtNotNull() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtColumnNullableAtNotNull)).isEqualTo("VARCHAR NOT NULL"); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotation - * {@linkplain javax.persistence.Column @Column}. When {@linkplain javax.persistence.Column#nullable nullable} from - * {@linkplain javax.persistence.Column @Column} is set to {@linkplain java.lang.Boolean#TRUE true} the return string - * should contain NOT NULL. - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationAtColumnNullable() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtColumnNullable)).isEqualTo("VARCHAR NOT NULL"); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotations - * {@linkplain javax.persistence.Column @Column} and {@linkplain jakarta.validation.constraints.NotNull @NotNull}. - * When either {@linkplain javax.persistence.Column#nullable nullable} from - * {@linkplain javax.persistence.Column @Column} is set to {@linkplain java.lang.Boolean#FALSE false} or the in this - * case redundant annotation {@linkplain jakarta.validation.constraints.NotNull @NotNull} is provided the return - * string should contain NOT NULL. - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationAtColumnNotNullableAtNotNull() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtColumnNotNullableAtNotNull)).isEqualTo("VARCHAR NOT NULL"); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotation - * {@linkplain javax.persistence.Column @Column} when the {@linkplain javax.persistence.Column#nullable nullable} - * option is set to {@linkplain java.lang.Boolean#FALSE false}. The return string should not contain NOT NULL. - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationAtColumnNotNullable() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtColumnNotNullable)).isEqualTo("VARCHAR"); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotation - * {@linkplain jakarta.validation.constraints.NotNull @NotNull}. The return string should not contain NOT NULL. - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationAtNotNull() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtNotNull)).isEqualTo("VARCHAR NOT NULL"); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotations - * {@linkplain jakarta.validation.constraints.Size @Size} and - * {@linkplain jakarta.validation.constraints.NotNull @NotNull}. The default value of - * {@linkplain jakarta.validation.constraints.Size#max() max(} from - * {@linkplain jakarta.validation.constraints.Size @Size} defaults to {@linkplain java.lang.Integer#MAX_VALUE - * Integer.max()} and therefore the result VARCHAR(2147483647) NOT NULL is expected. - * - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationAtSizeAtNotNull() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestAtSizeAtNotNull)).isEqualTo("VARCHAR(2147483647) NOT NULL"); - } - - /** - * Tests if {@linkplain SQLUtil#getSimpleSQLtype(Field)} returns the correct string based on the provided annotations - * {@linkplain javax.persistence.Column @Column}, {@linkplain jakarta.validation.constraints.Size @Size} and - * {@linkplain jakarta.validation.constraints.NotNull @NotNull}. The default value of - * {@linkplain jakarta.validation.constraints.Size#max() max(} from - * {@linkplain jakarta.validation.constraints.Size @Size} defaults to {@linkplain java.lang.Integer#MAX_VALUE - * Integer.max()} and therefore the result VARCHAR(2147483647) NOT NULL is expected. - * - * - */ - @Test - public void testGetSimpleSqlTypeAnnotationAtColumnNotNullableAtSizeAtNotNull() { - - assertThat(new SQLUtil().getSimpleSQLtype(fieldTestEntityAtColumnNotNullableAtSizeAtNotNull)) - .isEqualTo("VARCHAR(2147483647) NOT NULL"); - } - - /** - * Tests if {@linkplain SQLUtil#getFieldByName(Class, String)} finds a field in a certain class by its name. - */ - @Test - public void testGetFieldByNameSuccess() { - - assertThat(new SQLUtil().getFieldByName(testTestCatClass, "legs")).isEqualTo(testResultLegs); - } - - /** - * Tests if {@linkplain SQLUtil#getFieldByName(Class, String)} throws an {@linkplain java.lang.IllegalAccessError - * Error} when the {@linkplain java.lang.reflect.Field field} doesn't exist. - */ - @Test - public void testGetFieldByNameThrowsIllegalAccessErrorByField() { - - assertThrows(IllegalAccessError.class, () -> { - new SQLUtil().getFieldByName(testTestCatClass, null); - }); - } - - /** - * Tests if {@linkplain SQLUtil#getFieldByName(Class, String)} throws an {@linkplain java.lang.IllegalAccessError - * Error} when the class doesn't exist. - */ - @Test - public void testGetFieldByNameThrowsIllegalAccessErrorByClass() { - - assertThrows(IllegalAccessError.class, () -> { - new SQLUtil().getFieldByName(null, null); - }); - } - - /** - * Tests if {@linkplain SQLUtil#getFieldAnnotations(Class, String)} collects a {@linkplain java.lang.reflect.Field - * field's} annotations. - */ - @Test - public void testGetFieldAnnotationsSuccess() { - - final int annotationCountHasElements = 3; - final int annotationCountHasNoElements = 0; - - Condition hasElements = new Condition("A condition to ") { - @Override - public boolean matches(Annotation[] value) { - - return value.length == annotationCountHasElements || value.length == annotationCountHasNoElements; - } - }; - assertThat(new SQLUtil().getFieldAnnotations(testTestCatClass, "name")).has(hasElements); - assertThat(new SQLUtil().getFieldAnnotations(testTestCatClass, "legs")).has(hasElements); - } - - /** - * Tests if {@linkplain SQLUtil#getFieldAnnotations(Class, String)} throws an {@linkplain java.lang.IllegalAccessError - * Error} when the class doesn't exist. - */ - @Test - public void testGetFieldAnnotationsThrowsIllegalAccessErrorByClass() { - - assertThrows(IllegalAccessError.class, () -> { - new SQLUtil().getFieldByName(null, "legs"); - }); - - } - - /** - * Tests if {@linkplain SQLUtil#getFieldAnnotations(Class, String)} throws an {@linkplain java.lang.IllegalAccessError - * Error} when the provided field name can not be searched for. - */ - @Test - public void testGetFieldAnnotationsThrowsIllegalAccessErrorByFieldName() { - - assertThrows(IllegalAccessError.class, () -> { - new SQLUtil().getFieldByName(testTestCatClass, ""); - new SQLUtil().getFieldByName(testTestCatClass, null); - }); - - } - - /** - * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that - * was annotated with the option {@linkplain javax.persistence.Table#name() name} from the - * {@linkplain javax.persistence.Table @Table} annotation. - * - * @throws Exception - */ - @Test - public void testGetEntityTableNameAtTableSuccess() throws Exception { - - assertThat(new SQLUtil().getEntityTableName(fieldTestEntityAtTable)).isEqualTo("TEST_SIMPLE_ENTITY"); - - } - - /** - * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that - * was annotated with the option {@linkplain javax.persistence.Table#name() name} from the - * {@linkplain javax.persistence.Table @Table} annotation even when the provided class is an anonymous class. - */ - @Test - public void testGetEntityTableNameAtTableSuccessAnonymousClasses() throws Exception { - - assertThat(new SQLUtil().getEntityTableName(fieldTestAnonymousClassEntity)).isEqualTo("TEST_SIMPLE_ENTITY"); - - } - - /** - * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that - * was not annotated with the option {@linkplain javax.persistence.Table#name() name} from the - * {@linkplain javax.persistence.Table @Table} annotation. - * - * @throws Exception - */ - @Test - public void testGetEntityTableNameAtTableNameDefaultSuccess() throws Exception { - - assertThat(new SQLUtil().getEntityTableName(fieldTestEntityAtTableNameDefault)).isEqualTo("TestAnotherSimple"); - - } - - /** - * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that - * was not annotated with the option {@linkplain javax.persistence.Table#name() name} from the - * {@linkplain javax.persistence.Table @Table} annotation even when the provided class is an anonymous class. - */ - @Test - public void testGetEntityTableNameAtTableNameDefaultSuccessAnonymousClasses() throws Exception { - - assertThat(new SQLUtil().getEntityTableName(fieldTestAnonymousEntityAtTableNameDefault)) - .isEqualTo("TestAnotherSimple"); - - } - - /** - * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that - * was not annotated with the {@linkplain javax.persistence.Table @Table} annotation. - * - * @throws Exception - */ - @Test - public void testGetEntityTableNameAtTableNullSuccess() throws Exception { - - assertThat(new SQLUtil().getEntityTableName(fieldTestEntityAtTableNull)).isEqualTo("TestNotSoSimple"); - - } - - /** - * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} returns the table name of the provided entity class that - * was not annotated with the {@linkplain javax.persistence.Table @Table} annotation even when the provided class is - * an anonymous class. - * - * @throws Exception - */ - @Test - public void testGetEntityTableNameAtTableNNullSuccessAnonymousClasses() throws Exception { - - assertThat(new SQLUtil().getEntityTableName(fieldTestAnonymousEntityAtTableNull)).isEqualTo("TestNotSoSimple"); - - } - - /** - * Tests if {@linkplain SQLUtil#getEntityTableName(Field)} throws the Exception IllegalAccessError. - * - * @throws Exception - */ - @Test - public void testGetEntityTableNameThrowClassNotFoundExceptions() throws Exception { - - assertThrows(IllegalAccessError.class, () -> { - new SQLUtil().getEntityTableName(null); - }); - - } - - /** - * Tests if {@linkplain SQLUtil#getColumnName(Field)} returns the correct string based on the value in - * {@linkplain javax.persistence.Column#name() name } of the provided annotation - * {@linkplain javax.persistence.Column @Column}. - */ - @Test - public void testFieldGetColumnNameAtColumn() { - - assertThat(new SQLUtil().getColumnName(fieldGetColumnNameFieldAtColumn)).isEqualTo("FIELD_AT_COLUMN"); - } - - /** - * Tests if {@linkplain SQLUtil#getColumnName(Field)} returns the correct string when the option - * {@linkplain javax.persistence.Column#name() name } of the provided annotation - * {@linkplain javax.persistence.Column @Column} is defaulting to blank. - */ - @Test - public void testFieldGetColumnNameAtColumnBlank() { - - assertThat(new SQLUtil().getColumnName(fieldGetColumnNameFieldAtColumnBlank)) - .isEqualTo("testGetColumnNameFieldAtColumnBlank"); - } - - /** - * Tests if {@linkplain SQLUtil#getColumnName(Field)} returns the correct string when the provided annotation - * {@linkplain javax.persistence.Column @Column} is missing. - */ - @Test - public void testFieldGetColumnNameAtColumnMissing() { - - assertThat(new SQLUtil().getColumnName(fieldGetColumnNameFieldAtColumnMissing)) - .isEqualTo("testGetColumnNameFieldAtColumnMissing"); - } - - /** - * Tests if {@linkplain SQLUtil#getColumnName(Method)} returns the correct string based on the value in - * {@linkplain javax.persistence.Column#name() name } of the provided annotation - * {@linkplain javax.persistence.Column @Column}. - */ - @Test - public void testMethodGetColumnNameAtColumn() { - - assertThat(new SQLUtil().getColumnName(methodGetColumnNameMethodAtColumn)).isEqualTo("METHOD_AT_COLUMN"); - } - - /** - * Tests if {@linkplain SQLUtil#getColumnName(Method)} returns the correct string when the option - * {@linkplain javax.persistence.Column#name() name } of the provided annotation - * {@linkplain javax.persistence.Column @Column} is defaulting to blank. - */ - @Test - public void testMethodGetColumnNameAtColumnBlank() { - - assertThat(new SQLUtil().getColumnName(methodGetColumnNameMethodAtColumnBlank)) - .isEqualTo("getTestGetColumnNameMethodAtColumnBlank"); - } - - /** - * Tests if {@linkplain SQLUtil#getColumnName(Method)} returns the correct string when the provided annotation - * {@linkplain javax.persistence.Column @Column} is missing. - */ - @Test - public void testMethodGetColumnNameAtColumnMissing() { - - assertThat(new SQLUtil().getColumnName(methodGetColumnNameMethodAtColumnMissing)) - .isEqualTo("getTestGetColumnNameMethodAtColumnMissing"); - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} returns the correct string when a - * {@linkplain java.lang.reflect.Field field} of the provided class is annotated with the - * {@linkplain javax.persistence.Id @Id} annotation. - * - */ - @Test - public void testGetPrimaryKeyAtIdSuccess() { - - assertThat(new SQLUtil() - .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryone")) - .isEqualTo("java.lang.Long,id"); - - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} returns the correct string when a - * {@linkplain java.lang.reflect.Field field} of the provided class is annotated with the - * {@linkplain javax.persistence.Id @Id} annotation and the option and {@linkplain javax.persistence.Column#name() - * name} from {@linkplain javax.persistence.Column @Column}. - * - */ - @Test - public void testGetPrimaryKeyAtIdAtColumnSuccess() { - - assertThat(new SQLUtil() - .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primarytwo")) - .isEqualTo("java.lang.Long,TEST_ID"); - - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} returns the correct string when a method of the provided class - * is annotated with the {@linkplain javax.persistence.Id @Id} annotation. - * - */ - @Test - public void testGetPrimaryKeyMethodAtIdSuccess() { - - assertThat(new SQLUtil() - .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primarythree")) - .isEqualTo("java.lang.Long,getTestId"); - - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} returns the correct string when a method of the provided class - * is annotated with the {@linkplain javax.persistence.Id @Id} annotation and the option and - * {@linkplain javax.persistence.Column#name() name} from {@linkplain javax.persistence.Column @Column}. - */ - @Test - public void testGetPrimaryKeyMethodAtIdAtColumnSuccess() { - - assertThat(new SQLUtil() - .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryfour")) - .isEqualTo("java.lang.Long,TEST_ID"); - - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} throws the IllegalArgumentException when the provided class - * doesn't exist . - * - * @throws IllegalArgumentException when non existing class - */ - @Test - public void testGetPrimaryKeyNoClass() throws IllegalArgumentException { - - assertThrows(IllegalArgumentException.class, () -> { - new SQLUtil() - .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.NoClass"); - }); - - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} throws the IllegalArgumentError when the provided class doesn't - * exist . - * - * @throws IllegalAccessError when class object is null - */ - @Test - public void testGetPrimaryKeyStringNull() throws IllegalAccessError { - - assertThrows(IllegalAccessError.class, () -> { - new SQLUtil().getPrimaryKey(null); - }); - - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKey(String)} throws the IllegalArgumentError when the provided class doesn't - * have a {@linkplain java.lang.reflect.Field field} or a method annotated with the - * {@linkplain javax.persistence.Id @Id} annotation. - * - * @throws IllegalAccessError when class object is null - */ - @Test - public void testGetPrimaryKeyNoFieldNoMethod() throws IllegalAccessError { - - assertThrows(IllegalAccessError.class, () -> { - new SQLUtil() - .getPrimaryKey("com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys.Primaryfive"); - }); - - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values. - * - */ - @Test - public void testGetPrimaryKeyStatementAtId() { - - HashMap columnMap = new HashMap() { - { - put("name", "id"); - put("type", "BIGINT NOT NULL"); - } - }; - assertThat(new SQLUtil().getPrimaryKeyStatement(fieldTestAtId)).isEqualTo(columnMap); - - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values. - * - */ - @Test - public void testGetPrimaryKeyStatementAtIdAtGeneratedValue() { - - HashMap columnMap = new HashMap() { - { - put("name", "TEST_ID"); - put("type", "BIGINT NOT NULL AUTO INCREMENT"); - } - }; - assertThat(new SQLUtil().getPrimaryKeyStatement(fieldTestAtIdAtGeneratedValue)).isEqualTo(columnMap); - - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} throws the IllegalAccessError when the field is null. - */ - @Test - public void testGetPrimaryKeyStatementAtIdThrowsIllegalAccessError() { - - assertThrows(IllegalAccessError.class, () -> { - new SQLUtil().getPrimaryKeyStatement(null); - }); - - } - - /** - * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values even if the name was - * not provided. - * - */ - @Test - public void testGetPrimaryKeyStatementNameNull() { - - HashMap columnMap = new HashMap() { - { - put("name", "TEST_ID"); - put("type", "BIGINT NOT NULL AUTO INCREMENT"); - } - }; - assertThat(new SQLUtil().getPrimaryKeyStatement(fieldTestAtIdAtGeneratedValue, null)).isEqualTo(columnMap); - - } - - // /** - // * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values even if the name - // was - // * not provided. - // * - // */ - // @Test - // public void testgetForeignKeyNameWithAnnotationsSuccess() { - // - // assertThat(new SQLUtil().getForeignKeyName(fieldSimpleEntity, "test")).isEqualTo("simpleEntityId"); - // - // } - // - // /** - // * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values even if the name - // was - // * not provided. - // * - // */ - // @Test - // public void testgetForeignKeyNameWithAnnotationsSuccessWithDefaultName() { - // - // assertThat(new SQLUtil().getForeignKeyName(fieldSimpleEntityDefaultName, "test")).isEqualTo("simpleEntityId_test"); - // - // } - // - // /** - // * Tests if {@linkplain SQLUtil#getPrimaryKeyStatement(Field)} returns the correct HashMap values even if the name - // was - // * not provided. - // * - // */ - // @Test - // public void testgetForeignKeyNameWithAnnotationsSuccessFallbackIsNull() { - // - // assertThat(new SQLUtil().getForeignKeyName(fieldSimpleEntityDefaultName, - // null)).isEqualTo("simpleEntityId_default"); - // - // } -} \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLForeignKeyTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLForeignKeyTest.java deleted file mode 100644 index 50e8381ce3..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest/SQLForeignKeyTest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest; - -/** - * TODO leholm This type ... - * - */ -public class SQLForeignKeyTest { - -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java index 8737921b1c..989def8e46 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java @@ -3,7 +3,6 @@ import javax.persistence.Column; import javax.persistence.Id; -import com.devonfw.cobigen.templates.devon4j.test.utils.SQLUtilTest.SQLAnnotationTest; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestAnotherSimpleEntity; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestNotSoSimpleEntity; import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestSimpleEntity; From dcd7f0a2291c438856319e368f96634be77fee73 Mon Sep 17 00:00:00 2001 From: Lurian Date: Mon, 7 Nov 2022 15:52:52 +0100 Subject: [PATCH 25/39] Finished blueprint for template --- .../templates/devon4j/utils/SQLUtil.java | 22 ++++++++++++++----- ...eate_${variables.entityName}Entity.sql.ftl | 13 ++++++++++- .../templates/SQLTemplateGenerationTest.java | 2 ++ .../templates/testclasses/SQLTestEntity.java | 8 ++++--- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index b189f8ea37..3928c6e548 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -31,24 +31,36 @@ public String tableName(String entityName) { public String primaryKeyStatement(Map field) { - String fieldName; + String fieldName = getFieldName(field); Map annotations = getValue(field, "annotations"); - fieldName = getValue(field, "name"); String incrementType = "AUTO_INCREMENT"; return String.format("%s BIGINT %s PRIMARY KEY", fieldName, incrementType); } public String foreignKeyStatement(Map field) { + Map annotations = getValue(field, "annotations"); + Map joinColumnAnnotation = getValue(field, "javax_persistence_JoinColumn"); + String fieldName = getValue(field, "name"); + if (joinColumnAnnotation != null) { + + } + return ""; + } + + public String basicTypeStatement(Map field) { + return ""; + } + static private String getColumnConstraints(Map columnAnnotation) { return ""; } /** - * Extracts the name of the field from the Map whilst checking for name-overriding annotations. + * Extracts the name of the field from the Map whilst checking for name-override in @Column annotation */ static private String getFieldName(Map field) { String fieldName = chainAccess(field, new String[] { "annotations", "javax_persistence_Column", "name" }); - if (fieldName != null) { + if (fieldName != null && !fieldName.equals("")) { return fieldName; } else { return getValue(field, "name"); @@ -56,7 +68,7 @@ static private String getFieldName(Map field) { } /** - * Dynamic Helper function to navigate nested maps. Returns null on any type of error + * Helper function to navigate nested maps dynamically. Returns null on any type of error */ static private T chainAccess(Map map, String[] nestedFields) { try { diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl index e9a71a0903..f7a9ab575f 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -16,7 +16,18 @@ CREATE TABLE ${tableName} ( <#-- Primary Key statement --> <#if field.annotations.javax_persistence_Id??> <#assign statements += [SQLUtil.primaryKeyStatement(field)]> - <#elseif field.annotations.javax_persistence_JoinColumn> + <#-- OneToOne statement --> + <#elseif field.annotations.javax_persistence_OneToOne> + <#-- Key mapped on other table, skip --> + <#if field.annotations.javax_persistence_OneToOne.mappedBy != ""> + <#continue> + + <#assign statements += [SQLUtil.foreignKeyStatement(field)]> + <#-- OneToMany Foreign Keystatement --> + <#elseif field.annotations.javax_persistence_OneToMany> + <#assign statements += [SQLUtil.foreignKeyStatement(field)]> + <#else> + <#assign statements += [SQLUtil.basicTypeStatement(field)]> <#list statements as statement> diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java index afdc0b11c9..f5755eecf3 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java @@ -2,6 +2,8 @@ import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntity; import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; +import org.assertj.core.api.Assertions; +import org.junit.Assert; import org.junit.Test; public class SQLTemplateGenerationTest extends AbstractJavaTemplateTest { diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java index c3c2dbd91f..95ab1cf0b1 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java @@ -1,8 +1,6 @@ package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import javax.persistence.*; @Entity @Table(name = "SQLTEST") @@ -10,6 +8,9 @@ public class SQLTestEntity { @Id private Long id; + @Column + private Integer value; + public Long getId() { return id; } @@ -17,4 +18,5 @@ public Long getId() { public void setId(Long id) { this.id = id; } + } From 7baaebde9257deeebcd3d649936821e6a200755d Mon Sep 17 00:00:00 2001 From: Lurian Date: Tue, 8 Nov 2022 11:37:51 +0100 Subject: [PATCH 26/39] Implemented most functionality for template generation. Tests are lacking --- .../templates/devon4j/utils/SQLUtil.java | 146 ++++++++++++++++-- ...eate_${variables.entityName}Entity.sql.ftl | 33 ++-- .../templates/testclasses/EnumForTest.java | 7 + .../testclasses/ReferenceEntity.java | 19 +++ .../templates/testclasses/SQLTestEntity.java | 47 +++++- 5 files changed, 228 insertions(+), 24 deletions(-) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/EnumForTest.java create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/ReferenceEntity.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 3928c6e548..3a8c4d76bf 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -1,6 +1,7 @@ package com.devonfw.cobigen.templates.devon4j.utils; import java.util.*; +import java.util.function.Function; /** * Provides operations to identify and process SQL specific information @@ -19,7 +20,7 @@ public SQLUtil() { /** * Debug function to set breakpoint to analyze some data passed to the freemarkertemplate */ - public void debug(Map pojo) { + public void debug(Object obj) { System.out.println("DEBUG"); } @@ -33,37 +34,162 @@ public String primaryKeyStatement(Map field) { String fieldName = getFieldName(field); Map annotations = getValue(field, "annotations"); + Map columnAnnotations = getValue(annotations, "javax_persistence_Column"); + // Check for @Column to override default fieldname + if (columnAnnotations != null) { + String columnFieldName = getValue(columnAnnotations, "name"); + if (columnFieldName != null) + fieldName = columnFieldName; + } String incrementType = "AUTO_INCREMENT"; return String.format("%s BIGINT %s PRIMARY KEY", fieldName, incrementType); } public String foreignKeyStatement(Map field) { + Map annotations = getValue(field, "annotations"); - Map joinColumnAnnotation = getValue(field, "javax_persistence_JoinColumn"); - String fieldName = getValue(field, "name"); + Map joinColumnAnnotation = getValue(annotations, "javax_persistence_JoinColumn"); + String fieldName = getValue(field, "name"), fieldType = "BIGINT", + refTable = Objects.requireNonNull(getValue(field, "type")), refField = "id"; + Boolean unique = false, nullable = true; + + // Try extracting tablename from type following devonfw naming conventions: AwdeEntity -> AWDE + refTable = refTable.replaceAll("(Collection)|(List)|<|>", "").replace("Entity", "").toUpperCase(); + + // Try autogenerating foreign key name through naming convention + fieldName = fieldName.replace("Entity", "").toUpperCase() + "_ID"; + if (joinColumnAnnotation != null) { + // Parse @JoinColumn values and override defaults + String nameOverride = getValue(joinColumnAnnotation, "name"); + if (!Objects.equals(nameOverride, "")) + fieldName = nameOverride; + + String tableOverride = getValue(joinColumnAnnotation, "table"); + if (!Objects.equals(tableOverride, "")) + refTable = tableOverride; + unique = isUnique(joinColumnAnnotation); + nullable = isNullable(joinColumnAnnotation); } - return ""; + // Build column definition + String columnDef = fieldName + " " + fieldType; + if (unique) + columnDef += " UNIQUE"; + if (!nullable) + columnDef += " NOT NULL"; + // Build Foreign Key constraint and append it to column definition + String foreignKeyDef = String.format("FOREIGN KEY (%s) REFERENCES %s(%s)", fieldName, refTable, refField); + return columnDef + ", " + foreignKeyDef; } - public String basicTypeStatement(Map field) { - return ""; + public String basicStatement(Map field) { + + Map columnAnnotation = chainAccess(field, new String[] {"annotations", "javax_persistence_Column"}); + String fieldName = getFieldName(field), + typeString = Objects.requireNonNull(getValue(field, "type")), + fieldType = mapType(typeString); + Integer fieldLength = 255; + boolean nullable = true, + unique = false; + // Try to infer fieldType from possible annotations + Map enumerateAnnotation = chainAccess(field, new String[]{"annotations", "javax_persistence_Enumerated"}); + if (enumerateAnnotation != null) { + String enumType = Objects.requireNonNull(getValue(enumerateAnnotation, "value")); + if (enumType.equals("STRING")) { + fieldType = "VARCHAR"; + } else { + fieldType = "INTEGER"; + } + } + // Parse @Column if present + if (columnAnnotation != null) { + Integer columnLength = Integer.parseInt(Objects.requireNonNull(getValue(columnAnnotation, "length"))); + if (columnLength != null) fieldLength = columnLength; + nullable = isNullable(columnAnnotation); + unique = isUnique(columnAnnotation); + } + + // If fieldType is still empty throw exception + if (fieldType == null) throw new IllegalArgumentException("Couldn't map Java type to SQL type for typeString: " + typeString); + + // Add size to VARCHAR fields + if (fieldType.equals("VARCHAR")) { + fieldType = String.format("VARCHAR(%d)", fieldLength); + } + String statement = String.format("%s %s", fieldName, fieldType); + if (!nullable) statement += " NOT NULL"; + if (unique) statement += " UNIQUE"; + return statement; } - static private String getColumnConstraints(Map columnAnnotation) { - return ""; + /** + * Extracts value of nullable from @Column and @JoinColumn annotations + * @param columnAnnotation Map for the Column and JoinColumn annotations + */ + private static boolean isNullable(Map columnAnnotation) { + return Boolean.TRUE.equals(getValue(columnAnnotation, "unique")); + } + + /** + * Extracts value of unique from @Column and @JoinColumn annotations + * @param columnAnnotation Map for the Column and JoinColumn annotations + */ + private static boolean isUnique(Map columnAnnotation) { + return Boolean.TRUE.equals(getValue(columnAnnotation, "unique")); + } + + /** + * Helper function to map simple SQL types, returns null on unmappable type + */ + public static String mapType(String typeString) { + // Shortcut for case insensitive regex matching with start and ending ignore + Function match = (regex) -> typeString.matches(".*" + "(?i)" + regex + ".*"); + if (match.apply("(integer)|(int)")) { + return "INTEGER"; + } else if (match.apply("long")) { + return "BIGINT"; + } else if (match.apply("short")) { + return "SMALLINT"; + } else if (match.apply("BigDecimal")) { + return "NUMERIC"; + } else if (match.apply("String")) { + return "VARCHAR"; + } else if (match.apply("(char)|(Character)")) { + return "CHAR(1)"; + } else if (match.apply("byte")) { + return "TINYINT"; + } else if (match.apply("boolean")) { + return "BIT"; + } else if (match.apply("Date")) { + return "DATE"; + } else if (match.apply("Time")) { + return "TIME"; + } else if (match.apply("(Timestamp)|(Calendar)")) { + return "TIMESTAMP"; + } else if (match.apply("byte\\[\\]")) { + return "BLOB"; + } else if (match.apply("blob")) { + return "BLOB"; + } else if (match.apply("clob")) { + return "CLOB"; + } else if (match.apply("(Class)|(Locale)|(TimeZone)|(Currency)")) { + return "VARCHAR"; + }else { + return null; + } } /** * Extracts the name of the field from the Map whilst checking for name-override in @Column annotation */ static private String getFieldName(Map field) { + String fieldName = chainAccess(field, new String[] { "annotations", "javax_persistence_Column", "name" }); if (fieldName != null && !fieldName.equals("")) { return fieldName; } else { - return getValue(field, "name"); + return Objects.requireNonNull(getValue(field, "name")); } } @@ -71,6 +197,7 @@ static private String getFieldName(Map field) { * Helper function to navigate nested maps dynamically. Returns null on any type of error */ static private T chainAccess(Map map, String[] nestedFields) { + try { for (int i = 0; i < nestedFields.length - 1; i++) { String key = nestedFields[i]; @@ -86,6 +213,7 @@ static private T chainAccess(Map map, String[] nestedFields) { * Parametrized helper function to dynamically extract data from a map. Returns null on casting errors */ static private T getValue(Map map, String key) { + try { return (T) map.get(key); } catch (Exception ignored) { diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl index f7a9ab575f..4052dda1a2 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -6,35 +6,40 @@ ${SQLUtil.debug(pojo)} <#assign statements = []> <#assign joinTables = []> - -CREATE TABLE ${tableName} ( <#list pojo.methodAccessibleFields as field> - <#-- Skip Transient fields --> +<#-- Skip Transient fields --> <#if field.annotations.javax_persistence_Transient??> <#continue> - <#-- Primary Key statement --> - <#if field.annotations.javax_persistence_Id??> - <#assign statements += [SQLUtil.primaryKeyStatement(field)]> + <#elseif field.annotations.javax_persistence_Id??> + <#assign statements = statements + [SQLUtil.primaryKeyStatement(field)]> <#-- OneToOne statement --> - <#elseif field.annotations.javax_persistence_OneToOne> - <#-- Key mapped on other table, skip --> + <#elseif field.annotations.javax_persistence_OneToOne??> + <#-- Key mapped on other table, skip --> <#if field.annotations.javax_persistence_OneToOne.mappedBy != ""> <#continue> - <#assign statements += [SQLUtil.foreignKeyStatement(field)]> + <#assign statements = statements + [SQLUtil.foreignKeyStatement(field)]> <#-- OneToMany Foreign Keystatement --> - <#elseif field.annotations.javax_persistence_OneToMany> - <#assign statements += [SQLUtil.foreignKeyStatement(field)]> + <#elseif field.annotations.javax_persistence_OneToMany??> + <#assign statements = statements + [SQLUtil.foreignKeyStatement(field)]> + <#elseif field.annotations.javax_persistence_ManyToOne??> + <#-- Skip ManyToOne as it's just a Foreign Key on a different table --> + <#continue> + <#elseif field.annotations.javax_persistence_ManyToMany??> + <#-- Check if there is a JoinTable specified.... --> + <#continue> <#else> - <#assign statements += [SQLUtil.basicTypeStatement(field)]> + <#assign statements += [SQLUtil.basicStatement(field)]> +CREATE TABLE ${tableName} ( <#list statements as statement> ${statement}, ); - +<#-- TODO: parse generated JoinTables (NOT SURE IF NECESSARY! + AS TICKET MAYBE IMPLIES 1 SQL FILE FOR EACH ENTITY. +--> <#list joinTables as tb> - \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/EnumForTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/EnumForTest.java new file mode 100644 index 0000000000..b504f0b8d4 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/EnumForTest.java @@ -0,0 +1,7 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +public enum EnumForTest { + VALUE, + OK, + MAKING_THINGS_UP +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/ReferenceEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/ReferenceEntity.java new file mode 100644 index 0000000000..329b33b71c --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/ReferenceEntity.java @@ -0,0 +1,19 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +import javax.persistence.*; + +@Entity +public class ReferenceEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java index 95ab1cf0b1..bbbd58b48c 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java @@ -1,16 +1,26 @@ package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; import javax.persistence.*; +import java.util.List; @Entity @Table(name = "SQLTEST") public class SQLTestEntity { @Id + @Column(name = "MY_ID_FIELD") private Long id; - @Column + @Column(name = "VALUENAME") private Integer value; + @OneToOne + private ReferenceEntity refEntity; + + @Enumerated(EnumType.STRING) + private EnumForTest enumForTest; + + private List referenceEntities; + public Long getId() { return id; } @@ -19,4 +29,39 @@ public void setId(Long id) { this.id = id; } + public Integer getValue() { + return value; + } + + + public void setValue(Integer value) { + this.value = value; + } + + @OneToMany + @JoinColumn(name = "reference_entity_id") + public List getReferenceEntities() { + return referenceEntities; + } + + public void setReferenceEntities(List referenceEntities) { + this.referenceEntities = referenceEntities; + } + + + public ReferenceEntity getRefEntity() { + return refEntity; + } + + public void setRefEntity(ReferenceEntity refEntity) { + this.refEntity = refEntity; + } + + public EnumForTest getEnumForTest() { + return enumForTest; + } + + public void setEnumForTest(EnumForTest enumForTest) { + this.enumForTest = enumForTest; + } } From 59e2da504ea44762679693ce3e3668889b4b55f1 Mon Sep 17 00:00:00 2001 From: Lurian Date: Tue, 8 Nov 2022 11:46:29 +0100 Subject: [PATCH 27/39] Simplified usage of AbstractJavaTemplateTest. Reduced functionality to process(JavaClass) method --- .../templates/AbstractJavaTemplateTest.java | 35 ++++++++++--------- .../templates/SQLTemplateGenerationTest.java | 14 +++++--- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java index 0d93bcd715..a7f4378aef 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java @@ -85,32 +85,33 @@ public void addObject(String key, Object instance) { } /** - * Consumes current test configuration to produce an output! - * @return Produced file - */ - public String process() { - StringWriter out = new StringWriter(); - engine.process(template, model, out, "UTF-8"); - return out.toString(); - } - - /** - * Auto initialization for test. For more fine-grained control initialize your own configuration with the - * helper methods. - * @param templatePath Path to the template relative to subproject source root - * @param utils Classes to auto-instanciate and inject into freemarker for the template + * Consumes the given class to produce the template * @param modelClass Class to auto-generate reflective pojo model + * @return Produced file */ - public void defaultInit(String templatePath, Class modelClass, Class[] utils) { - Path tp = Paths.get(templatePath); + public String process(Class modelClass) { + Path tp = Paths.get(getTemplatePath()); String filename = tp.getFileName().toString(); Path templateFolder = tp.getParent(); model = new JavaInputReader().createModel(modelClass); template = createTemplate(filename, templateFolder); engine = new FreeMarkerTemplateEngine(); engine.setTemplateFolder(templateFolder); - for (Class utilClass : utils) { + for (Class utilClass : getUtils()) { addUtil(utilClass); } + StringWriter out = new StringWriter(); + engine.process(template, model, out, "UTF-8"); + return out.toString(); } + + /** + * @return utils Classes to auto-instanciate and inject into freemarker for the template + */ + abstract public Class[] getUtils(); + + /** + * @return templatePath Path to the template relative to subproject source root + */ + abstract public String getTemplatePath(); } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java index f5755eecf3..6032c14909 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java @@ -7,12 +7,18 @@ import org.junit.Test; public class SQLTemplateGenerationTest extends AbstractJavaTemplateTest { - @Test public void generateSQLTest() { + String output = this.process(SQLTestEntity.class); + } + + @Override + public Class[] getUtils() { + return new Class[] { SQLUtil.class }; + } - this.defaultInit("src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl", - SQLTestEntity.class, new Class[] { SQLUtil.class }); - String output = this.process(); + @Override + public String getTemplatePath() { + return "src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl"; } } From 8e7a6e04081f891f291e682f5b8e0fb7284059f7 Mon Sep 17 00:00:00 2001 From: Lurian Date: Tue, 8 Nov 2022 14:26:39 +0100 Subject: [PATCH 28/39] Fixes to Nullable and Unique constraint parsing for @Column and @JoinColumn annotations --- .../com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java | 4 ++-- .../devon4j/test/templates/testclasses/SQLTestEntity.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 3a8c4d76bf..e43b2ff188 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -128,7 +128,7 @@ public String basicStatement(Map field) { * @param columnAnnotation Map for the Column and JoinColumn annotations */ private static boolean isNullable(Map columnAnnotation) { - return Boolean.TRUE.equals(getValue(columnAnnotation, "unique")); + return Boolean.parseBoolean(getValue(columnAnnotation, "nullable")); } /** @@ -136,7 +136,7 @@ private static boolean isNullable(Map columnAnnotation) { * @param columnAnnotation Map for the Column and JoinColumn annotations */ private static boolean isUnique(Map columnAnnotation) { - return Boolean.TRUE.equals(getValue(columnAnnotation, "unique")); + return Boolean.parseBoolean(getValue(columnAnnotation, "unique")); } /** diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java index bbbd58b48c..23b94a8ad4 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java @@ -13,10 +13,11 @@ public class SQLTestEntity { @Column(name = "VALUENAME") private Integer value; - @OneToOne + @OneToOne(mappedBy = "I_am_mapped!!_and_should_be_skipped!") private ReferenceEntity refEntity; @Enumerated(EnumType.STRING) + @Column(length = 420, name = "YES_EXCACTLY") private EnumForTest enumForTest; private List referenceEntities; @@ -39,7 +40,7 @@ public void setValue(Integer value) { } @OneToMany - @JoinColumn(name = "reference_entity_id") + @JoinColumn(name = "reference_entity_id", unique = true, nullable = false) public List getReferenceEntities() { return referenceEntities; } From 8ab95abd61c3b05469b7d033b6211d2f11f0598e Mon Sep 17 00:00:00 2001 From: Lurian Date: Wed, 9 Nov 2022 08:10:05 +0100 Subject: [PATCH 29/39] Fixed logic error pertaining @OneToMany and @ManyToOne annotations --- ...eate_${variables.entityName}Entity.sql.ftl | 19 +++++++++---------- .../templates/testclasses/SQLTestEntity.java | 17 ++++++++--------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl index 4052dda1a2..01ad07c463 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -12,23 +12,24 @@ ${SQLUtil.debug(pojo)} <#continue> <#-- Primary Key statement --> <#elseif field.annotations.javax_persistence_Id??> - <#assign statements = statements + [SQLUtil.primaryKeyStatement(field)]> + <#assign statements += [SQLUtil.primaryKeyStatement(field)]> <#-- OneToOne statement --> <#elseif field.annotations.javax_persistence_OneToOne??> <#-- Key mapped on other table, skip --> <#if field.annotations.javax_persistence_OneToOne.mappedBy != ""> <#continue> - <#assign statements = statements + [SQLUtil.foreignKeyStatement(field)]> - <#-- OneToMany Foreign Keystatement --> + <#assign statements += [SQLUtil.foreignKeyStatement(field)]> + <#-- Skip OneToMany as it's just a Foreign Key on a different table --> <#elseif field.annotations.javax_persistence_OneToMany??> - <#assign statements = statements + [SQLUtil.foreignKeyStatement(field)]> - <#elseif field.annotations.javax_persistence_ManyToOne??> - <#-- Skip ManyToOne as it's just a Foreign Key on a different table --> <#continue> + <#-- ManyToOne: create Foreign Keystatement from the field --> + <#elseif field.annotations.javax_persistence_ManyToOne??> + <#assign statements += [SQLUtil.foreignKeyStatement(field)]> + <#-- TODO: Check if there is a JoinTable specified that should be created. --> <#elseif field.annotations.javax_persistence_ManyToMany??> - <#-- Check if there is a JoinTable specified.... --> <#continue> + <#-- Try generating simple SQL statement from field --> <#else> <#assign statements += [SQLUtil.basicStatement(field)]> @@ -38,8 +39,6 @@ CREATE TABLE ${tableName} ( ${statement}, ); -<#-- TODO: parse generated JoinTables (NOT SURE IF NECESSARY! - AS TICKET MAYBE IMPLIES 1 SQL FILE FOR EACH ENTITY. ---> +<#-- TODO: parse generated JoinTables --> <#list joinTables as tb> \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java index 23b94a8ad4..dff999c71c 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java @@ -11,13 +11,13 @@ public class SQLTestEntity { private Long id; @Column(name = "VALUENAME") - private Integer value; + private Integer integerValue; - @OneToOne(mappedBy = "I_am_mapped!!_and_should_be_skipped!") + @OneToOne private ReferenceEntity refEntity; @Enumerated(EnumType.STRING) - @Column(length = 420, name = "YES_EXCACTLY") + @Column(length = 420, name = "ENUM_TEST_FIELD_NAME_OVERRIDE") private EnumForTest enumForTest; private List referenceEntities; @@ -30,17 +30,16 @@ public void setId(Long id) { this.id = id; } - public Integer getValue() { - return value; + public Integer getIntegerValue() { + return integerValue; } - public void setValue(Integer value) { - this.value = value; + public void setIntegerValue(Integer value) { + this.integerValue = value; } - @OneToMany - @JoinColumn(name = "reference_entity_id", unique = true, nullable = false) + @OneToMany(mappedBy = "I_SHALL_BE_SKIPPED") public List getReferenceEntities() { return referenceEntities; } From b480ef05e8dd2bded8ec33674c4b7491d9e3a46f Mon Sep 17 00:00:00 2001 From: Felix Berger Date: Wed, 9 Nov 2022 12:07:49 +0100 Subject: [PATCH 30/39] #860 Added first tests for sql generation --- .../templates/devon4j/utils/SQLUtil.java | 35 +- .../templates/SQLTemplateGenerationTest.java | 31 +- .../testclasses/SQLTestEntityDataTypes.java | 424 ++++++++++++++++++ .../devon4j/test/utils/SQLUtilTest.java | 40 ++ 4 files changed, 512 insertions(+), 18 deletions(-) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index e43b2ff188..dcc122978d 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -1,6 +1,7 @@ package com.devonfw.cobigen.templates.devon4j.utils; -import java.util.*; +import java.util.Map; +import java.util.Objects; import java.util.function.Function; /** @@ -85,15 +86,14 @@ public String foreignKeyStatement(Map field) { public String basicStatement(Map field) { - Map columnAnnotation = chainAccess(field, new String[] {"annotations", "javax_persistence_Column"}); - String fieldName = getFieldName(field), - typeString = Objects.requireNonNull(getValue(field, "type")), + Map columnAnnotation = chainAccess(field, new String[] { "annotations", "javax_persistence_Column" }); + String fieldName = getFieldName(field), typeString = Objects.requireNonNull(getValue(field, "type")), fieldType = mapType(typeString); Integer fieldLength = 255; - boolean nullable = true, - unique = false; + boolean nullable = true, unique = false; // Try to infer fieldType from possible annotations - Map enumerateAnnotation = chainAccess(field, new String[]{"annotations", "javax_persistence_Enumerated"}); + Map enumerateAnnotation = chainAccess(field, + new String[] { "annotations", "javax_persistence_Enumerated" }); if (enumerateAnnotation != null) { String enumType = Objects.requireNonNull(getValue(enumerateAnnotation, "value")); if (enumType.equals("STRING")) { @@ -105,37 +105,45 @@ public String basicStatement(Map field) { // Parse @Column if present if (columnAnnotation != null) { Integer columnLength = Integer.parseInt(Objects.requireNonNull(getValue(columnAnnotation, "length"))); - if (columnLength != null) fieldLength = columnLength; + if (columnLength != null) + fieldLength = columnLength; nullable = isNullable(columnAnnotation); unique = isUnique(columnAnnotation); } // If fieldType is still empty throw exception - if (fieldType == null) throw new IllegalArgumentException("Couldn't map Java type to SQL type for typeString: " + typeString); + if (fieldType == null) + throw new IllegalArgumentException("Couldn't map Java type to SQL type for typeString: " + typeString); // Add size to VARCHAR fields if (fieldType.equals("VARCHAR")) { fieldType = String.format("VARCHAR(%d)", fieldLength); } String statement = String.format("%s %s", fieldName, fieldType); - if (!nullable) statement += " NOT NULL"; - if (unique) statement += " UNIQUE"; + if (!nullable) + statement += " NOT NULL"; + if (unique) + statement += " UNIQUE"; return statement; } /** * Extracts value of nullable from @Column and @JoinColumn annotations + * * @param columnAnnotation Map for the Column and JoinColumn annotations */ private static boolean isNullable(Map columnAnnotation) { + return Boolean.parseBoolean(getValue(columnAnnotation, "nullable")); } /** * Extracts value of unique from @Column and @JoinColumn annotations + * * @param columnAnnotation Map for the Column and JoinColumn annotations */ private static boolean isUnique(Map columnAnnotation) { + return Boolean.parseBoolean(getValue(columnAnnotation, "unique")); } @@ -143,8 +151,9 @@ private static boolean isUnique(Map columnAnnotation) { * Helper function to map simple SQL types, returns null on unmappable type */ public static String mapType(String typeString) { + // Shortcut for case insensitive regex matching with start and ending ignore - Function match = (regex) -> typeString.matches(".*" + "(?i)" + regex + ".*"); + Function match = (regex) -> typeString.matches("(?i).*" + regex + ".*"); if (match.apply("(integer)|(int)")) { return "INTEGER"; } else if (match.apply("long")) { @@ -175,7 +184,7 @@ public static String mapType(String typeString) { return "CLOB"; } else if (match.apply("(Class)|(Locale)|(TimeZone)|(Currency)")) { return "VARCHAR"; - }else { + } else { return null; } } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java index 6032c14909..1e34995e6b 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java @@ -1,24 +1,45 @@ package com.devonfw.cobigen.templates.devon4j.test.templates; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntity; +import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntityDataTypes; import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; public class SQLTemplateGenerationTest extends AbstractJavaTemplateTest { @Test public void generateSQLTest() { - String output = this.process(SQLTestEntity.class); + + String output = process(SQLTestEntity.class); } @Override public Class[] getUtils() { + return new Class[] { SQLUtil.class }; } @Override public String getTemplatePath() { - return "src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl"; + + return "src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl"; + } + + /** + * Test the correct generation of data types + */ + @Test + public void testDatatypeMapping() { + + String ouptut = process(SQLTestEntityDataTypes.class); + assertThat(ouptut).contains("_timestamp2 TIMESTAMP").contains("_blob2 BLOB").contains("_bit BIT,") + .contains("_date DATE").contains("_tinyint TINYINT").contains("_integer2 INTEGER").contains("_bigint BIGINT") + .contains("_varchar3 VARCHAR").contains("_integer1 INTEGER").contains("_varchar4 VARCHAR") + .contains("_clob CLOB").contains("_blob BLOB").contains("_varchar VARCHAR").contains("_char2 CHAR(1)") + .contains(" _smallint SMALLINT").contains(" _char CHAR(1)").contains("_timestamp TIMESTAMP") + .contains("_time TIME").contains("_numeric NUMERIC").contains("_varchar2 VARCHAR"); + } } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java new file mode 100644 index 0000000000..2ada680372 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java @@ -0,0 +1,424 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +import java.math.BigDecimal; +import java.security.Timestamp; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Time; +import java.util.Calendar; +import java.util.Currency; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * Test class to test sql data type mapping + * + */ +@Entity +@Table(name = "SQLDataTypeTest") +public class SQLTestEntityDataTypes { + + @Column() + private int _integer1; + + @Column() + private Integer _integer2; + + @Column() + private long _bigint; + + @Column() + private short _smallint; + + @Column() + private BigDecimal _numeric; + + @Column() + private String _varchar; + + @Column() + private char _char; + + @Column() + private Character _char2; + + @Column() + private byte _tinyint; + + @Column() + private boolean _bit; + + @Column() + private Date _date; + + @Column() + private Time _time; + + @Column() + private Timestamp _timestamp; + + @Column() + private Calendar _timestamp2; + + @Column() + private byte[] _blob; + + @Column() + private Blob _blob2; + + @Column() + private Clob _clob; + + @Column() + private Class _varchar2; + + @Column() + private Locale _varchar3; + + private TimeZone _varchar4; + + @Column() + private Currency _varchar5; + + /** + * @return _integer1 + */ + public int get_integer1() { + + return this._integer1; + } + + /** + * @param _integer1 new value of {@link #get_integer1}. + */ + public void set_integer1(int _integer1) { + + this._integer1 = _integer1; + } + + /** + * @return _integer2 + */ + public Integer get_integer2() { + + return this._integer2; + } + + /** + * @param _integer2 new value of {@link #get_integer2}. + */ + public void set_integer2(Integer _integer2) { + + this._integer2 = _integer2; + } + + /** + * @return _bigint + */ + public long get_bigint() { + + return this._bigint; + } + + /** + * @param _bigint new value of {@link #get_bigint}. + */ + public void set_bigint(long _bigint) { + + this._bigint = _bigint; + } + + /** + * @return _smallint + */ + public short get_smallint() { + + return this._smallint; + } + + /** + * @param _smallint new value of {@link #get_smallint}. + */ + public void set_smallint(short _smallint) { + + this._smallint = _smallint; + } + + /** + * @return _numeric + */ + public BigDecimal get_numeric() { + + return this._numeric; + } + + /** + * @param _numeric new value of {@link #get_numeric}. + */ + public void set_numeric(BigDecimal _numeric) { + + this._numeric = _numeric; + } + + /** + * @return _varchar + */ + public String get_varchar() { + + return this._varchar; + } + + /** + * @param _varchar new value of {@link #get_varchar}. + */ + public void set_varchar(String _varchar) { + + this._varchar = _varchar; + } + + /** + * @return _char + */ + public char get_char() { + + return this._char; + } + + /** + * @param _char new value of {@link #get_char}. + */ + public void set_char(char _char) { + + this._char = _char; + } + + /** + * @return _character + */ + public Character get_character() { + + return this._character; + } + + /** + * @param _character new value of {@link #get_character}. + */ + public void set_character(Character _character) { + + this._character = _character; + } + + /** + * @return _tinyint + */ + public byte get_tinyint() { + + return this._tinyint; + } + + /** + * @param _tinyint new value of {@link #get_tinyint}. + */ + public void set_tinyint(byte _tinyint) { + + this._tinyint = _tinyint; + } + + /** + * @return _bit + */ + public boolean is_bit() { + + return this._bit; + } + + /** + * @param _bit new value of {@link #get_bit}. + */ + public void set_bit(boolean _bit) { + + this._bit = _bit; + } + + /** + * @return _date + */ + public Date get_date() { + + return this._date; + } + + /** + * @param _date new value of {@link #get_date}. + */ + public void set_date(Date _date) { + + this._date = _date; + } + + /** + * @return _time + */ + public Time get_time() { + + return this._time; + } + + /** + * @param _time new value of {@link #get_time}. + */ + public void set_time(Time _time) { + + this._time = _time; + } + + /** + * @return _timestamp + */ + public Timestamp get_timestamp() { + + return this._timestamp; + } + + /** + * @param _timestamp new value of {@link #get_timestamp}. + */ + public void set_timestamp(Timestamp _timestamp) { + + this._timestamp = _timestamp; + } + + /** + * @return _timestamp2 + */ + public Calendar get_timestamp2() { + + return this._timestamp2; + } + + /** + * @param _timestamp2 new value of {@link #get_timestamp2}. + */ + public void set_timestamp2(Calendar _timestamp2) { + + this._timestamp2 = _timestamp2; + } + + /** + * @return _blob + */ + public byte[] get_blob() { + + return this._blob; + } + + /** + * @param _blob new value of {@link #get_blob}. + */ + public void set_blob(byte[] _blob) { + + this._blob = _blob; + } + + /** + * @return _blob2 + */ + public Blob get_blob2() { + + return this._blob2; + } + + /** + * @param _blob2 new value of {@link #get_blob2}. + */ + public void set_blob2(Blob _blob2) { + + this._blob2 = _blob2; + } + + /** + * @return _clob + */ + public Clob get_clob() { + + return this._clob; + } + + /** + * @param _clob new value of {@link #get_clob}. + */ + public void set_clob(Clob _clob) { + + this._clob = _clob; + } + + // /** + // * @return _varchar2 + // */ + // public Class get_varchar2() { + // + // return this._varchar2; + // } + + // /** + // * @param _varchar2 new value of {@link #get_varchar2}. + // */ + // public void set_varchar2(Class _varchar2) { + // + // this._varchar2 = _varchar2; + // } + + /** + * @return _varchar3 + */ + public Locale get_varchar3() { + + return this._varchar3; + } + + /** + * @param _varchar3 new value of {@link #get_varchar3}. + */ + public void set_varchar3(Locale _varchar3) { + + this._varchar3 = _varchar3; + } + + /** + * @return _varchar4 + */ + public TimeZone get_varchar4() { + + return this._varchar4; + } + + /** + * @param _varchar4 new value of {@link #get_varchar4}. + */ + public void set_varchar4(TimeZone _varchar4) { + + this._varchar4 = _varchar4; + } + + /** + * @return _carchar5 + */ + public Currency get_carchar5() { + + return this._varchar5; + } + + /** + * @param _carchar5 new value of {@link #get_carchar5}. + */ + public void set_carchar5(Currency _carchar5) { + + this._varchar5 = _carchar5; + } + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index 1962ac57e4..b220511c0b 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -1,5 +1,45 @@ package com.devonfw.cobigen.templates.devon4j.test.utils; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; + public class SQLUtilTest { + @Test + public void testClassTypeMapping() { + + // Test fails + assertThat(SQLUtil.mapType("Class")).isEqualTo("VARCHAR"); + } + + @Test + public void testByteArray() { + + // Test fails + assertThat(SQLUtil.mapType("byte[]")).isEqualTo("BLOB"); + } + + @Test + public void testTimestamp() { + + // Test fails + assertThat(SQLUtil.mapType("Timestamp")).isEqualTo("TIMESTAMP"); + } + + @Test + public void testTimeZone() { + + // Test fails + assertThat(SQLUtil.mapType("TimeZone")).isEqualTo("VARCHAR"); + } + + @Test + public void testCalendar() { + + assertThat(SQLUtil.mapType("Calendar")).isEqualTo("TIMESTAMP"); + } + } From 5008c4e362c778d6e2c82ad2f9eda180bc55de16 Mon Sep 17 00:00:00 2001 From: Felix Berger Date: Thu, 10 Nov 2022 08:14:06 +0100 Subject: [PATCH 31/39] #860 added more tests, fixed bugs --- .../templates/SQLTemplateGenerationTest.java | 45 ++- .../templates/testclasses/SQLTestEntity.java | 83 +++-- .../testclasses/SQLTestEntityDataTypes.java | 299 +++++++++--------- .../testclasses/SQLTestEntityForeignKeys.java | 61 ++++ .../devon4j/test/utils/SQLUtilTest.java | 5 + 5 files changed, 286 insertions(+), 207 deletions(-) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityForeignKeys.java diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java index 1e34995e6b..f0dd94dba9 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java @@ -6,14 +6,14 @@ import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntity; import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntityDataTypes; +import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntityForeignKeys; import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; +/** + * Test class for SQL template generation + * + */ public class SQLTemplateGenerationTest extends AbstractJavaTemplateTest { - @Test - public void generateSQLTest() { - - String output = process(SQLTestEntity.class); - } @Override public Class[] getUtils() { @@ -28,18 +28,39 @@ public String getTemplatePath() { } /** - * Test the correct generation of data types + * Tests the correct generation of the enumerated type, the primary key, and name overriding + */ + @Test + public void testSQLEntity() { + + String output = process(SQLTestEntity.class); + assertThat(output).contains("CREATE TABLE SQLTEST").contains("ENUM_TEST_FIELD_NAME_OVERRIDE VARCHAR(420)") + .contains("MY_ID_FIELD BIGINT AUTO_INCREMENT PRIMARY KEY"); + } + + /** + * Tests the correct generation of data types */ @Test public void testDatatypeMapping() { String ouptut = process(SQLTestEntityDataTypes.class); - assertThat(ouptut).contains("_timestamp2 TIMESTAMP").contains("_blob2 BLOB").contains("_bit BIT,") - .contains("_date DATE").contains("_tinyint TINYINT").contains("_integer2 INTEGER").contains("_bigint BIGINT") - .contains("_varchar3 VARCHAR").contains("_integer1 INTEGER").contains("_varchar4 VARCHAR") - .contains("_clob CLOB").contains("_blob BLOB").contains("_varchar VARCHAR").contains("_char2 CHAR(1)") - .contains(" _smallint SMALLINT").contains(" _char CHAR(1)").contains("_timestamp TIMESTAMP") - .contains("_time TIME").contains("_numeric NUMERIC").contains("_varchar2 VARCHAR"); + assertThat(ouptut).contains("timestamp2 TIMESTAMP").contains("blob2 BLOB").contains("bit BIT,") + .contains("date DATE").contains("tinyint TINYINT").contains("integer2 INTEGER").contains("bigint BIGINT") + .contains("varchar3 VARCHAR").contains("integer1 INTEGER").contains("varchar4 VARCHAR").contains("clob CLOB") + .contains("blob BLOB").contains("varchar VARCHAR").contains("char2 CHAR(1)").contains("smallint SMALLINT") + .contains("char1 CHAR(1)").contains("timestamp TIMESTAMP").contains("time TIME").contains("numeric NUMERIC") + .contains("varchar2 VARCHAR").contains("CREATE TABLE SQLDataTypeTest").contains("varchar5 VARCHAR"); + + } + + /** + * Tests the correct generation of foreign key statements + */ + @Test + public void testForeignKeyStatements() { + String output = process(SQLTestEntityForeignKeys.class); + assertThat(output).contains("test_id BIGINT, FOREIGN KEY (test_id) REFERENCES SQLTEST(id)"); } } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java index dff999c71c..b4c97bfeda 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java @@ -1,67 +1,58 @@ package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; -import javax.persistence.*; -import java.util.List; - +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * Test entity to test the correct generation of the enumerated type, the primary key, and name overriding + * + */ @Entity @Table(name = "SQLTEST") public class SQLTestEntity { - @Id - @Column(name = "MY_ID_FIELD") - private Long id; - - @Column(name = "VALUENAME") - private Integer integerValue; + @Id + @Column(name = "MY_ID_FIELD") + private Long id; - @OneToOne - private ReferenceEntity refEntity; + @Column(name = "VALUENAME") + private Integer integerValue; - @Enumerated(EnumType.STRING) - @Column(length = 420, name = "ENUM_TEST_FIELD_NAME_OVERRIDE") - private EnumForTest enumForTest; + @Enumerated(EnumType.STRING) + @Column(length = 420, name = "ENUM_TEST_FIELD_NAME_OVERRIDE") + private EnumForTest enumForTest; - private List referenceEntities; + public Long getId() { - public Long getId() { - return id; - } + return this.id; + } - public void setId(Long id) { - this.id = id; - } + public void setId(Long id) { - public Integer getIntegerValue() { - return integerValue; - } + this.id = id; + } + public Integer getIntegerValue() { - public void setIntegerValue(Integer value) { - this.integerValue = value; - } + return this.integerValue; + } - @OneToMany(mappedBy = "I_SHALL_BE_SKIPPED") - public List getReferenceEntities() { - return referenceEntities; - } + public void setIntegerValue(Integer value) { - public void setReferenceEntities(List referenceEntities) { - this.referenceEntities = referenceEntities; - } + this.integerValue = value; + } + public EnumForTest getEnumForTest() { - public ReferenceEntity getRefEntity() { - return refEntity; - } + return this.enumForTest; + } - public void setRefEntity(ReferenceEntity refEntity) { - this.refEntity = refEntity; - } + public void setEnumForTest(EnumForTest enumForTest) { - public EnumForTest getEnumForTest() { - return enumForTest; - } + this.enumForTest = enumForTest; + } - public void setEnumForTest(EnumForTest enumForTest) { - this.enumForTest = enumForTest; - } } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java index 2ada680372..9e64a64974 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java @@ -24,401 +24,402 @@ public class SQLTestEntityDataTypes { @Column() - private int _integer1; + private int integer1; @Column() - private Integer _integer2; + private Integer integer2; @Column() - private long _bigint; + private long bigint; @Column() - private short _smallint; + private short smallint; @Column() - private BigDecimal _numeric; + private BigDecimal numeric; @Column() - private String _varchar; + private String varchar; @Column() - private char _char; + private char char1; @Column() - private Character _char2; + private Character char2; @Column() - private byte _tinyint; + private byte tinyint; @Column() - private boolean _bit; + private boolean bit; @Column() - private Date _date; + private Date date; @Column() - private Time _time; + private Time time; @Column() - private Timestamp _timestamp; + private Timestamp timestamp; @Column() - private Calendar _timestamp2; + private Calendar timestamp2; @Column() - private byte[] _blob; + private byte[] blob; @Column() - private Blob _blob2; + private Blob blob2; @Column() - private Clob _clob; + private Clob clob; - @Column() - private Class _varchar2; + // @Column() + // private Class varchar2; @Column() - private Locale _varchar3; + private Locale varchar3; - private TimeZone _varchar4; + @Column() + private TimeZone varchar4; @Column() - private Currency _varchar5; + private Currency varchar5; /** - * @return _integer1 + * @return integer1 */ - public int get_integer1() { + public int getInteger1() { - return this._integer1; + return this.integer1; } /** - * @param _integer1 new value of {@link #get_integer1}. + * @param integer1 new value of {@link #getinteger1}. */ - public void set_integer1(int _integer1) { + public void setInteger1(int integer1) { - this._integer1 = _integer1; + this.integer1 = integer1; } /** - * @return _integer2 + * @return integer2 */ - public Integer get_integer2() { + public Integer getInteger2() { - return this._integer2; + return this.integer2; } /** - * @param _integer2 new value of {@link #get_integer2}. + * @param integer2 new value of {@link #getinteger2}. */ - public void set_integer2(Integer _integer2) { + public void setInteger2(Integer integer2) { - this._integer2 = _integer2; + this.integer2 = integer2; } /** - * @return _bigint + * @return bigint */ - public long get_bigint() { + public long getBigint() { - return this._bigint; + return this.bigint; } /** - * @param _bigint new value of {@link #get_bigint}. + * @param bigint new value of {@link #getbigint}. */ - public void set_bigint(long _bigint) { + public void setBigint(long bigint) { - this._bigint = _bigint; + this.bigint = bigint; } /** - * @return _smallint + * @return smallint */ - public short get_smallint() { + public short getSmallint() { - return this._smallint; + return this.smallint; } /** - * @param _smallint new value of {@link #get_smallint}. + * @param smallint new value of {@link #getsmallint}. */ - public void set_smallint(short _smallint) { + public void setSmallint(short smallint) { - this._smallint = _smallint; + this.smallint = smallint; } /** - * @return _numeric + * @return numeric */ - public BigDecimal get_numeric() { + public BigDecimal getNumeric() { - return this._numeric; + return this.numeric; } /** - * @param _numeric new value of {@link #get_numeric}. + * @param numeric new value of {@link #getnumeric}. */ - public void set_numeric(BigDecimal _numeric) { + public void setNumeric(BigDecimal numeric) { - this._numeric = _numeric; + this.numeric = numeric; } /** - * @return _varchar + * @return varchar */ - public String get_varchar() { + public String getVarchar() { - return this._varchar; + return this.varchar; } /** - * @param _varchar new value of {@link #get_varchar}. + * @param varchar new value of {@link #getvarchar}. */ - public void set_varchar(String _varchar) { + public void setVarchar(String varchar) { - this._varchar = _varchar; + this.varchar = varchar; } /** - * @return _char + * @return char1 */ - public char get_char() { + public char getChar1() { - return this._char; + return this.char1; } /** - * @param _char new value of {@link #get_char}. + * @param char1 new value of {@link #getchar1}. */ - public void set_char(char _char) { + public void setChar1(char char1) { - this._char = _char; + this.char1 = char1; } /** - * @return _character + * @return char2 */ - public Character get_character() { + public Character getChar2() { - return this._character; + return this.char2; } /** - * @param _character new value of {@link #get_character}. + * @param char2 new value of {@link #getchar2}. */ - public void set_character(Character _character) { + public void setChar2(Character char2) { - this._character = _character; + this.char2 = char2; } /** - * @return _tinyint + * @return tinyint */ - public byte get_tinyint() { + public byte getTinyint() { - return this._tinyint; + return this.tinyint; } /** - * @param _tinyint new value of {@link #get_tinyint}. + * @param tinyint new value of {@link #gettinyint}. */ - public void set_tinyint(byte _tinyint) { + public void setTinyint(byte tinyint) { - this._tinyint = _tinyint; + this.tinyint = tinyint; } /** - * @return _bit + * @return bit */ - public boolean is_bit() { + public boolean isBit() { - return this._bit; + return this.bit; } /** - * @param _bit new value of {@link #get_bit}. + * @param bit new value of {@link #getbit}. */ - public void set_bit(boolean _bit) { + public void setBit(boolean bit) { - this._bit = _bit; + this.bit = bit; } /** - * @return _date + * @return date */ - public Date get_date() { + public Date getDate() { - return this._date; + return this.date; } /** - * @param _date new value of {@link #get_date}. + * @param date new value of {@link #getdate}. */ - public void set_date(Date _date) { + public void setDate(Date date) { - this._date = _date; + this.date = date; } /** - * @return _time + * @return time */ - public Time get_time() { + public Time getTime() { - return this._time; + return this.time; } /** - * @param _time new value of {@link #get_time}. + * @param time new value of {@link #gettime}. */ - public void set_time(Time _time) { + public void setTime(Time time) { - this._time = _time; + this.time = time; } /** - * @return _timestamp + * @return timestamp */ - public Timestamp get_timestamp() { + public Timestamp getTimestamp() { - return this._timestamp; + return this.timestamp; } /** - * @param _timestamp new value of {@link #get_timestamp}. + * @param timestamp new value of {@link #gettimestamp}. */ - public void set_timestamp(Timestamp _timestamp) { + public void setTimestamp(Timestamp timestamp) { - this._timestamp = _timestamp; + this.timestamp = timestamp; } /** - * @return _timestamp2 + * @return timestamp2 */ - public Calendar get_timestamp2() { + public Calendar getTimestamp2() { - return this._timestamp2; + return this.timestamp2; } /** - * @param _timestamp2 new value of {@link #get_timestamp2}. + * @param timestamp2 new value of {@link #gettimestamp2}. */ - public void set_timestamp2(Calendar _timestamp2) { + public void setTimestamp2(Calendar timestamp2) { - this._timestamp2 = _timestamp2; + this.timestamp2 = timestamp2; } /** - * @return _blob + * @return blob */ - public byte[] get_blob() { + public byte[] getBlob() { - return this._blob; + return this.blob; } /** - * @param _blob new value of {@link #get_blob}. + * @param blob new value of {@link #getblob}. */ - public void set_blob(byte[] _blob) { + public void setBlob(byte[] blob) { - this._blob = _blob; + this.blob = blob; } /** - * @return _blob2 + * @return blob2 */ - public Blob get_blob2() { + public Blob getBlob2() { - return this._blob2; + return this.blob2; } /** - * @param _blob2 new value of {@link #get_blob2}. + * @param blob2 new value of {@link #getblob2}. */ - public void set_blob2(Blob _blob2) { + public void setBlob2(Blob blob2) { - this._blob2 = _blob2; + this.blob2 = blob2; } /** - * @return _clob + * @return clob */ - public Clob get_clob() { + public Clob getClob() { - return this._clob; + return this.clob; } /** - * @param _clob new value of {@link #get_clob}. + * @param clob new value of {@link #getclob}. */ - public void set_clob(Clob _clob) { + public void setClob(Clob clob) { - this._clob = _clob; + this.clob = clob; } // /** - // * @return _varchar2 + // * @return varchar2 // */ - // public Class get_varchar2() { + // public Class getVarchar2() { // - // return this._varchar2; + // return this.varchar2; // } - + // // /** - // * @param _varchar2 new value of {@link #get_varchar2}. + // * @param varchar2 new value of {@link #getvarchar2}. // */ - // public void set_varchar2(Class _varchar2) { + // public void setVarchar2(Class varchar2) { // - // this._varchar2 = _varchar2; + // this.varchar2 = varchar2; // } /** - * @return _varchar3 + * @return varchar3 */ - public Locale get_varchar3() { + public Locale getVarchar3() { - return this._varchar3; + return this.varchar3; } /** - * @param _varchar3 new value of {@link #get_varchar3}. + * @param varchar3 new value of {@link #getvarchar3}. */ - public void set_varchar3(Locale _varchar3) { + public void setVarchar3(Locale varchar3) { - this._varchar3 = _varchar3; + this.varchar3 = varchar3; } /** - * @return _varchar4 + * @return varchar4 */ - public TimeZone get_varchar4() { + public TimeZone getVarchar4() { - return this._varchar4; + return this.varchar4; } /** - * @param _varchar4 new value of {@link #get_varchar4}. + * @param varchar4 new value of {@link #getvarchar4}. */ - public void set_varchar4(TimeZone _varchar4) { + public void setVarchar4(TimeZone varchar4) { - this._varchar4 = _varchar4; + this.varchar4 = varchar4; } /** - * @return _carchar5 + * @return varchar5 */ - public Currency get_carchar5() { + public Currency getVarchar5() { - return this._varchar5; + return this.varchar5; } /** - * @param _carchar5 new value of {@link #get_carchar5}. + * @param varchar5 new value of {@link #getvarchar5}. */ - public void set_carchar5(Currency _carchar5) { + public void setVarchar5(Currency varchar5) { - this._varchar5 = _carchar5; + this.varchar5 = varchar5; } } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityForeignKeys.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityForeignKeys.java new file mode 100644 index 0000000000..d148539afe --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityForeignKeys.java @@ -0,0 +1,61 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +/** + * Test entity to test the correct generation of the enumerated type, the primary key, and name overriding + * + */ +@Entity +@Table(name = "SQLTEST") +public class SQLTestEntityForeignKeys { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private Long id; + + @OneToOne() + @JoinColumn(referencedColumnName = "MY_ID_FIELD", name = "test_id") + private SQLTestEntity sqlTestEntity; + + /** + * @return id + */ + public Long getId() { + + return this.id; + } + + /** + * @param id new value of {@link #getid}. + */ + public void setId(Long id) { + + this.id = id; + } + + /** + * @return sqlTestEntity + */ + public SQLTestEntity getSqlTestEntity() { + + return this.sqlTestEntity; + } + + /** + * @param sqlTestEntity new value of {@link #getsqlTestEntity}. + */ + public void setSqlTestEntity(SQLTestEntity sqlTestEntity) { + + this.sqlTestEntity = sqlTestEntity; + } + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index b220511c0b..3ae02063e1 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -6,6 +6,11 @@ import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; +/** + * + * Test class for {@link SQLUtil} + * + */ public class SQLUtilTest { @Test From c7e18793c59a962b32f495c3be06dcb67c2cb775 Mon Sep 17 00:00:00 2001 From: Lurian Date: Wed, 9 Nov 2022 11:12:58 +0100 Subject: [PATCH 32/39] Implemented parsing of @JoinTable annotation into a separate inline SQL Table for the ManyToMany relationships. --- .../templates/devon4j/utils/SQLUtil.java | 82 ++++++++++++++++--- ...eate_${variables.entityName}Entity.sql.ftl | 36 +++++++- 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index dcc122978d..76f39c0322 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -1,7 +1,6 @@ package com.devonfw.cobigen.templates.devon4j.utils; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.Function; /** @@ -26,9 +25,60 @@ public void debug(Object obj) { System.out.println("DEBUG"); } - public String tableName(String entityName) { + /** + * Unwraps type to autogenerate a name for the table following devonf naming convention. + * @param entityType String that represents the entity class type + * @return parsed table name + */ + public String tableName(String entityType) { + return entityType.replaceFirst(".*<", "") + .replaceFirst(">.*", "") + .replace("Entity", "") + .toUpperCase(); + } - return entityName.replace("Entity", "").toUpperCase(); + /** + * Parses a @JoinColumn annotation directly into a Foreign Key statement for a @JoinTable + * + * @param joinColumnAnnotation + * @param defaultTableName Possible to pass TableName in case it's not specified in the annotation and has to be implied from + * context + * @return + */ + public String parseJoinColumn(Map joinColumnAnnotation, String defaultTableName) { + + Function extract = (fieldKey) -> Objects.requireNonNull(getValue(joinColumnAnnotation, fieldKey)); + String name = extract.apply("name"); + boolean nullable = Boolean.parseBoolean(extract.apply("nullable")); + boolean unique = Boolean.parseBoolean(extract.apply("unique")); + String table = extract.apply("table"); + String referencedColumnName = extract.apply("referencedColumnName"); + // Check if some fields haven't been defined and replace with defaults + if (referencedColumnName.equals("")) { + referencedColumnName = "ID"; + } + if (table.equals("")) { + table = defaultTableName; + } + if (table.equals("")) { + throw new IllegalStateException( + "Cannot infer name for reference table! Error encountered while parsing JoinColumnAnnotation: " + + joinColumnAnnotation.toString()); + } + // If the name is empty build the fieldName by appending ID to the tableName + if (name.equals("")) { + name = table + "_ID"; + } + String statement = name + " BIGINT"; + if (unique) { + statement += " UNIQUE"; + } + if (!nullable) { + statement += " NOT NULL"; + } + String foreignKeyDef = String.format("FOREIGN KEY (%s) REFERENCES %s(%s)", name, table, referencedColumnName); + + return statement + ", " + foreignKeyDef; } public String primaryKeyStatement(Map field) { @@ -50,18 +100,19 @@ public String foreignKeyStatement(Map field) { Map annotations = getValue(field, "annotations"); Map joinColumnAnnotation = getValue(annotations, "javax_persistence_JoinColumn"); - String fieldName = getValue(field, "name"), fieldType = "BIGINT", - refTable = Objects.requireNonNull(getValue(field, "type")), refField = "id"; + String fieldName = Objects.requireNonNull(getValue(field, "name")), fieldType = "BIGINT", + refTable = Objects.requireNonNull(getValue(field, "type")), referencedColumnName = "id"; Boolean unique = false, nullable = true; // Try extracting tablename from type following devonfw naming conventions: AwdeEntity -> AWDE - refTable = refTable.replaceAll("(Collection)|(List)|<|>", "").replace("Entity", "").toUpperCase(); + refTable = refTable.replace("Entity", "").toUpperCase(); // Try autogenerating foreign key name through naming convention fieldName = fieldName.replace("Entity", "").toUpperCase() + "_ID"; + // Parse @JoinColumn values and override defaults if values are present if (joinColumnAnnotation != null) { - // Parse @JoinColumn values and override defaults + // Each field is extracted and controlled, if present override the defaults. String nameOverride = getValue(joinColumnAnnotation, "name"); if (!Objects.equals(nameOverride, "")) fieldName = nameOverride; @@ -70,6 +121,10 @@ public String foreignKeyStatement(Map field) { if (!Objects.equals(tableOverride, "")) refTable = tableOverride; + String refColOverride = getValue(joinColumnAnnotation, "referencedColumnName"); + if (!Objects.equals(refColOverride, "")) + referencedColumnName = refColOverride; + unique = isUnique(joinColumnAnnotation); nullable = isNullable(joinColumnAnnotation); } @@ -80,7 +135,8 @@ public String foreignKeyStatement(Map field) { if (!nullable) columnDef += " NOT NULL"; // Build Foreign Key constraint and append it to column definition - String foreignKeyDef = String.format("FOREIGN KEY (%s) REFERENCES %s(%s)", fieldName, refTable, refField); + String foreignKeyDef = String.format("FOREIGN KEY (%s) REFERENCES %s(%s)", fieldName, refTable, + referencedColumnName); return columnDef + ", " + foreignKeyDef; } @@ -129,7 +185,7 @@ public String basicStatement(Map field) { /** * Extracts value of nullable from @Column and @JoinColumn annotations - * + * * @param columnAnnotation Map for the Column and JoinColumn annotations */ private static boolean isNullable(Map columnAnnotation) { @@ -139,7 +195,7 @@ private static boolean isNullable(Map columnAnnotation) { /** * Extracts value of unique from @Column and @JoinColumn annotations - * + * * @param columnAnnotation Map for the Column and JoinColumn annotations */ private static boolean isUnique(Map columnAnnotation) { @@ -153,7 +209,7 @@ private static boolean isUnique(Map columnAnnotation) { public static String mapType(String typeString) { // Shortcut for case insensitive regex matching with start and ending ignore - Function match = (regex) -> typeString.matches("(?i).*" + regex + ".*"); + Function match = (regex) -> typeString.matches(".*" + "(?i)" + regex + ".*"); if (match.apply("(integer)|(int)")) { return "INTEGER"; } else if (match.apply("long")) { @@ -212,7 +268,7 @@ static private T chainAccess(Map map, String[] nestedFields) { String key = nestedFields[i]; map = getValue(map, key); } - return getValue(map, nestedFields[nestedFields.length - 1]); + return (T) getValue(map, nestedFields[nestedFields.length - 1]); } catch (Exception ignored) { return null; } diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl index 01ad07c463..2f3fff0f96 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -1,4 +1,3 @@ -${SQLUtil.debug(pojo)} <#if pojo.annotations.javax_persistence_Table??> <#assign tableName = pojo.annotations.javax_persistence_Table.name> <#else> @@ -27,8 +26,33 @@ ${SQLUtil.debug(pojo)} <#elseif field.annotations.javax_persistence_ManyToOne??> <#assign statements += [SQLUtil.foreignKeyStatement(field)]> <#-- TODO: Check if there is a JoinTable specified that should be created. --> - <#elseif field.annotations.javax_persistence_ManyToMany??> - <#continue> + <#elseif field.annotations.javax_persistence_ManyToMany?? && field.annotations.javax_persistence_JoinTable??> + <#assign joinTableAnnotation = field.annotations.javax_persistence_JoinTable> + + <#-- Parse joinColumns to generate Foreign Keys --> + <#assign joinColumns = joinTableAnnotation.joinColumns> + <#assign inverseJoinColumns = joinTableAnnotation.inverseJoinColumns> + <#-- Statement collector list --> + <#assign statements = [] > + <#assign defaultFieldTable = SQLUtil.tableName(field.type)> + <#list joinColumns as jcol> + <#assign jcolAnnotation = jcol.javax_persistence_JoinColumn> + ${SQLUtil.debug(defaultFieldTable)} + <#assign statements += [SQLUtil.parseJoinColumn(jcolAnnotation, defaultFieldTable)]> + + + <#-- When parsing inverse join columns pass the tableName as a default for the reference to this parsed Entity --> + <#list inverseJoinColumns as jcol> + <#assign jcolAnnotation = jcol.javax_persistence_JoinColumn> + <#assign statements += [SQLUtil.parseJoinColumn(jcolAnnotation, tableName)]> + + <#-- Build joinTable with parsed data --> + <#assign joinTable = {}> + <#assign joinTable += { "statements": statements }> + <#assign joinTable += { "name": joinTableAnnotation.name }> + <#-- Append result to collector list --> + <#assign joinTables += [joinTable]> + <#-- Try generating simple SQL statement from field --> <#else> <#assign statements += [SQLUtil.basicStatement(field)]> @@ -41,4 +65,10 @@ CREATE TABLE ${tableName} ( ); <#-- TODO: parse generated JoinTables --> <#list joinTables as tb> +CREATE TABLE ${tb.name} ( + ID BIGINT AUTO_INCREMENT PRIMARY KEY, +<#list tb.statements as statement> + ${statement}, + +); \ No newline at end of file From 568760b18f6cf37fabe18aae79e7b86122e02d7c Mon Sep 17 00:00:00 2001 From: Lurian Date: Thu, 10 Nov 2022 09:42:02 +0100 Subject: [PATCH 33/39] - Removed some unneccessary Blob/Clob logic - Fixed test assertions - Fixed SQL Type mapping regex and matching order - Implemented test for @JoinTable and @ManyToMany --- .../templates/devon4j/utils/SQLUtil.java | 37 ++++------- ...eate_${variables.entityName}Entity.sql.ftl | 1 - .../templates/SQLTemplateGenerationTest.java | 33 ++++++---- ...Types.java => SQLTestDataTypesEntity.java} | 63 ++++--------------- ...eys.java => SQLTestForeignKeysEntity.java} | 2 +- .../testclasses/SQLTestJoinTableEntity.java | 35 +++++++++++ 6 files changed, 82 insertions(+), 89 deletions(-) rename cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/{SQLTestEntityDataTypes.java => SQLTestDataTypesEntity.java} (85%) rename cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/{SQLTestEntityForeignKeys.java => SQLTestForeignKeysEntity.java} (96%) create mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestJoinTableEntity.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 76f39c0322..701fb382fd 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -17,32 +17,23 @@ public SQLUtil() { // Empty for CobiGen to automatically instantiate it } - /** - * Debug function to set breakpoint to analyze some data passed to the freemarkertemplate - */ - public void debug(Object obj) { - - System.out.println("DEBUG"); - } - /** * Unwraps type to autogenerate a name for the table following devonf naming convention. + * * @param entityType String that represents the entity class type * @return parsed table name */ public String tableName(String entityType) { - return entityType.replaceFirst(".*<", "") - .replaceFirst(">.*", "") - .replace("Entity", "") - .toUpperCase(); + + return entityType.replaceFirst(".*<", "").replaceFirst(">.*", "").replace("Entity", "").toUpperCase(); } /** * Parses a @JoinColumn annotation directly into a Foreign Key statement for a @JoinTable * * @param joinColumnAnnotation - * @param defaultTableName Possible to pass TableName in case it's not specified in the annotation and has to be implied from - * context + * @param defaultTableName Possible to pass TableName in case it's not specified in the annotation and has to be + * implied from context * @return */ public String parseJoinColumn(Map joinColumnAnnotation, String defaultTableName) { @@ -209,7 +200,7 @@ private static boolean isUnique(Map columnAnnotation) { public static String mapType(String typeString) { // Shortcut for case insensitive regex matching with start and ending ignore - Function match = (regex) -> typeString.matches(".*" + "(?i)" + regex + ".*"); + Function match = (regex) -> typeString.matches("(?i).*" + "(" + regex + ")" + ".*"); if (match.apply("(integer)|(int)")) { return "INTEGER"; } else if (match.apply("long")) { @@ -222,24 +213,20 @@ public static String mapType(String typeString) { return "VARCHAR"; } else if (match.apply("(char)|(Character)")) { return "CHAR(1)"; + } else if (match.apply("byte\\[\\]")) { + return "BLOB"; } else if (match.apply("byte")) { return "TINYINT"; } else if (match.apply("boolean")) { return "BIT"; } else if (match.apply("Date")) { return "DATE"; - } else if (match.apply("Time")) { - return "TIME"; - } else if (match.apply("(Timestamp)|(Calendar)")) { - return "TIMESTAMP"; - } else if (match.apply("byte\\[\\]")) { - return "BLOB"; - } else if (match.apply("blob")) { - return "BLOB"; - } else if (match.apply("clob")) { - return "CLOB"; } else if (match.apply("(Class)|(Locale)|(TimeZone)|(Currency)")) { return "VARCHAR"; + } else if (match.apply("(Timestamp)|(Calendar)")) { + return "TIMESTAMP"; + } else if (match.apply("Time")) { + return "TIME"; } else { return null; } diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl index 2f3fff0f96..91c519eec9 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -37,7 +37,6 @@ <#assign defaultFieldTable = SQLUtil.tableName(field.type)> <#list joinColumns as jcol> <#assign jcolAnnotation = jcol.javax_persistence_JoinColumn> - ${SQLUtil.debug(defaultFieldTable)} <#assign statements += [SQLUtil.parseJoinColumn(jcolAnnotation, defaultFieldTable)]> diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java index f0dd94dba9..fe67048b8c 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java @@ -1,12 +1,13 @@ package com.devonfw.cobigen.templates.devon4j.test.templates; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; +import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestJoinTableEntity; import org.junit.Test; import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntity; -import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntityDataTypes; -import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntityForeignKeys; +import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestDataTypesEntity; +import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestForeignKeysEntity; import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; /** @@ -31,7 +32,7 @@ public String getTemplatePath() { * Tests the correct generation of the enumerated type, the primary key, and name overriding */ @Test - public void testSQLEntity() { + public void testEnumType() { String output = process(SQLTestEntity.class); assertThat(output).contains("CREATE TABLE SQLTEST").contains("ENUM_TEST_FIELD_NAME_OVERRIDE VARCHAR(420)") @@ -44,13 +45,13 @@ public void testSQLEntity() { @Test public void testDatatypeMapping() { - String ouptut = process(SQLTestEntityDataTypes.class); - assertThat(ouptut).contains("timestamp2 TIMESTAMP").contains("blob2 BLOB").contains("bit BIT,") - .contains("date DATE").contains("tinyint TINYINT").contains("integer2 INTEGER").contains("bigint BIGINT") - .contains("varchar3 VARCHAR").contains("integer1 INTEGER").contains("varchar4 VARCHAR").contains("clob CLOB") - .contains("blob BLOB").contains("varchar VARCHAR").contains("char2 CHAR(1)").contains("smallint SMALLINT") + String ouptut = process(SQLTestDataTypesEntity.class); + assertThat(ouptut).contains("timestamp2 TIMESTAMP").contains("bit BIT,").contains("date DATE") + .contains("tinyint TINYINT").contains("integer2 INTEGER").contains("bigint BIGINT") + .contains("varchar3 VARCHAR(255)").contains("integer1 INTEGER").contains("varchar4 VARCHAR(255)") + .contains("blob BLOB").contains("varchar VARCHAR(255)").contains("char2 CHAR(1)").contains("smallint SMALLINT") .contains("char1 CHAR(1)").contains("timestamp TIMESTAMP").contains("time TIME").contains("numeric NUMERIC") - .contains("varchar2 VARCHAR").contains("CREATE TABLE SQLDataTypeTest").contains("varchar5 VARCHAR"); + .contains("varchar2 VARCHAR(255)").contains("CREATE TABLE SQLDataTypeTest").contains("varchar5 VARCHAR(255)"); } @@ -60,7 +61,15 @@ public void testDatatypeMapping() { @Test public void testForeignKeyStatements() { - String output = process(SQLTestEntityForeignKeys.class); - assertThat(output).contains("test_id BIGINT, FOREIGN KEY (test_id) REFERENCES SQLTEST(id)"); + String output = process(SQLTestForeignKeysEntity.class); + assertThat(output).contains("test_id BIGINT, FOREIGN KEY (test_id) REFERENCES SQLTEST(MY_ID_FIELD)"); + } + + @Test + public void testJoinTableGeneration() { + String output = process(SQLTestJoinTableEntity.class); + assertThat(output).contains("CREATE TABLE MY_AWESOME_JOINTABLE") + .contains("REF_ENTITY_ID BIGINT UNIQUE, FOREIGN KEY (REF_ENTITY_ID) REFERENCES REFERENCE(OVERRIDE_ID)") + .contains("SQLTESTJOINTABLE_ID BIGINT, FOREIGN KEY (SQLTESTJOINTABLE_ID) REFERENCES SQLTESTJOINTABLE(ID)"); } } diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestDataTypesEntity.java similarity index 85% rename from cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java rename to cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestDataTypesEntity.java index 9e64a64974..fab85d0204 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityDataTypes.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestDataTypesEntity.java @@ -2,8 +2,6 @@ import java.math.BigDecimal; import java.security.Timestamp; -import java.sql.Blob; -import java.sql.Clob; import java.sql.Time; import java.util.Calendar; import java.util.Currency; @@ -21,7 +19,7 @@ */ @Entity @Table(name = "SQLDataTypeTest") -public class SQLTestEntityDataTypes { +public class SQLTestDataTypesEntity { @Column() private int integer1; @@ -68,14 +66,8 @@ public class SQLTestEntityDataTypes { @Column() private byte[] blob; - @Column() - private Blob blob2; - - @Column() - private Clob clob; - - // @Column() - // private Class varchar2; + @Column() + private Class varchar2; @Column() private Locale varchar3; @@ -326,53 +318,24 @@ public void setBlob(byte[] blob) { this.blob = blob; } - /** - * @return blob2 - */ - public Blob getBlob2() { - - return this.blob2; - } - /** - * @param blob2 new value of {@link #getblob2}. - */ - public void setBlob2(Blob blob2) { - this.blob2 = blob2; - } - /** - * @return clob + /** + * @return varchar2 */ - public Clob getClob() { + public Class getVarchar2() { - return this.clob; - } + return this.varchar2; + } - /** - * @param clob new value of {@link #getclob}. + /** + * @param varchar2 new value of {@link #getvarchar2}. */ - public void setClob(Clob clob) { - - this.clob = clob; - } + public void setVarchar2(Class varchar2) { - // /** - // * @return varchar2 - // */ - // public Class getVarchar2() { - // - // return this.varchar2; - // } - // - // /** - // * @param varchar2 new value of {@link #getvarchar2}. - // */ - // public void setVarchar2(Class varchar2) { - // - // this.varchar2 = varchar2; - // } + this.varchar2 = varchar2; + } /** * @return varchar3 diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityForeignKeys.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestForeignKeysEntity.java similarity index 96% rename from cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityForeignKeys.java rename to cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestForeignKeysEntity.java index d148539afe..b634e861b9 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntityForeignKeys.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestForeignKeysEntity.java @@ -15,7 +15,7 @@ */ @Entity @Table(name = "SQLTEST") -public class SQLTestEntityForeignKeys { +public class SQLTestForeignKeysEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestJoinTableEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestJoinTableEntity.java new file mode 100644 index 0000000000..b28402fdac --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestJoinTableEntity.java @@ -0,0 +1,35 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +import javax.persistence.*; +import java.util.Set; + +@Entity +public class SQLTestJoinTableEntity { + @Id + @Column(name = "id", nullable = false) + private Long id; + + @ManyToMany + @JoinTable( + name = "MY_AWESOME_JOINTABLE", + joinColumns = @JoinColumn(name = "REF_ENTITY_ID", table = "REFERENCE", unique = true, referencedColumnName = "OVERRIDE_ID"), + inverseJoinColumns = @JoinColumn + ) + private Set referenceEntities; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Set getReferenceEntities() { + return referenceEntities; + } + + public void setReferenceEntities(Set referenceEntities) { + this.referenceEntities = referenceEntities; + } +} From 1da70f4e1d8ade2b69ebb843193500d777e10100 Mon Sep 17 00:00:00 2001 From: Lurian Date: Tue, 15 Nov 2022 10:23:31 +0100 Subject: [PATCH 34/39] Adressing change-request: - removal of dead classes/files - addition of javadoc --- .../cobigen-eclipse-test/.classpath | 2 - cobigen-templates/sql-openapi-app/context.xml | 13 -- .../sql-openapi-app/templates.xml | 0 .../templates-devon4j/.gitignore | 1 - .../templates/devon4j/utils/SQLUtil.java | 35 +++++- .../main/templates/sql_java_app/templates.xml | 2 - .../templates/AbstractJavaTemplateTest.java | 14 +-- .../utils/resources/sqltest/TestAnimal.java | 46 ------- .../test/utils/resources/sqltest/TestCat.java | 36 ------ .../sqltest/TestSqlTypeAnnotations.java | 116 ------------------ .../entities/TestAnotherSimpleEntity.java | 49 -------- .../entities/TestNotSoSimpleEntity.java | 47 ------- .../sqltest/entities/TestSimpleEntity.java | 60 --------- .../sqltest/enums/TestSimpleEnum.java | 11 -- .../sqltest/primarykeys/Primaryfive.java | 14 --- .../sqltest/primarykeys/Primaryfour.java | 19 --- .../sqltest/primarykeys/Primaryone.java | 12 -- .../sqltest/primarykeys/Primarythree.java | 17 --- .../sqltest/primarykeys/Primarytwo.java | 18 --- 19 files changed, 33 insertions(+), 479 deletions(-) delete mode 100644 cobigen-templates/sql-openapi-app/context.xml delete mode 100644 cobigen-templates/sql-openapi-app/templates.xml delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestNotSoSimpleEntity.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/enums/TestSimpleEnum.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfour.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryone.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarythree.java delete mode 100644 cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java diff --git a/cobigen-eclipse/cobigen-eclipse-test/.classpath b/cobigen-eclipse/cobigen-eclipse-test/.classpath index 831f611b54..de7cb12fd3 100644 --- a/cobigen-eclipse/cobigen-eclipse-test/.classpath +++ b/cobigen-eclipse/cobigen-eclipse-test/.classpath @@ -30,7 +30,5 @@ - - diff --git a/cobigen-templates/sql-openapi-app/context.xml b/cobigen-templates/sql-openapi-app/context.xml deleted file mode 100644 index be138dda6d..0000000000 --- a/cobigen-templates/sql-openapi-app/context.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/cobigen-templates/sql-openapi-app/templates.xml b/cobigen-templates/sql-openapi-app/templates.xml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cobigen-templates/templates-devon4j/.gitignore b/cobigen-templates/templates-devon4j/.gitignore index a2aebfea6f..e69de29bb2 100644 --- a/cobigen-templates/templates-devon4j/.gitignore +++ b/cobigen-templates/templates-devon4j/.gitignore @@ -1 +0,0 @@ -.classpath \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 701fb382fd..c177668d3b 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -9,6 +9,8 @@ */ public class SQLUtil extends CommonUtil { + private static int DEFAULT_FIELD_LENGTH = 255; + /** * The constructor. */ @@ -34,7 +36,7 @@ public String tableName(String entityType) { * @param joinColumnAnnotation * @param defaultTableName Possible to pass TableName in case it's not specified in the annotation and has to be * implied from context - * @return + * @return column + foreign key constraint for this @JoinColumn annotation */ public String parseJoinColumn(Map joinColumnAnnotation, String defaultTableName) { @@ -72,6 +74,11 @@ public String parseJoinColumn(Map joinColumnAnnotation, String defaul return statement + ", " + foreignKeyDef; } + /** + * Generates a primary key statement from the given field + * @param field Dynamic hashmap containing field data + * @return SQL Primary key statement for table as String + */ public String primaryKeyStatement(Map field) { String fieldName = getFieldName(field); @@ -87,6 +94,11 @@ public String primaryKeyStatement(Map field) { return String.format("%s BIGINT %s PRIMARY KEY", fieldName, incrementType); } + /** + * Generates a foreign key statement from the given field + * @param field Dynamic hashmap containing field data + * @return SQL Foreign key statement as String + */ public String foreignKeyStatement(Map field) { Map annotations = getValue(field, "annotations"); @@ -131,12 +143,17 @@ public String foreignKeyStatement(Map field) { return columnDef + ", " + foreignKeyDef; } + /** + * Basic SQL column statements derived from field hashmaps + * @param field Dynamic hashmap with field data + * @return Basic SQL statement as String + */ public String basicStatement(Map field) { Map columnAnnotation = chainAccess(field, new String[] { "annotations", "javax_persistence_Column" }); String fieldName = getFieldName(field), typeString = Objects.requireNonNull(getValue(field, "type")), fieldType = mapType(typeString); - Integer fieldLength = 255; + Integer fieldLength = DEFAULT_FIELD_LENGTH; boolean nullable = true, unique = false; // Try to infer fieldType from possible annotations Map enumerateAnnotation = chainAccess(field, @@ -151,9 +168,7 @@ public String basicStatement(Map field) { } // Parse @Column if present if (columnAnnotation != null) { - Integer columnLength = Integer.parseInt(Objects.requireNonNull(getValue(columnAnnotation, "length"))); - if (columnLength != null) - fieldLength = columnLength; + fieldLength = Integer.parseInt(Objects.requireNonNull(getValue(columnAnnotation, "length"))); nullable = isNullable(columnAnnotation); unique = isUnique(columnAnnotation); } @@ -195,7 +210,9 @@ private static boolean isUnique(Map columnAnnotation) { } /** - * Helper function to map simple SQL types, returns null on unmappable type + * Helper function to map simple Java types to SQL types, returns null on unmappable type + * @param typeString JavaType as String (int, Long, String...) + * @return SQLType as String */ public static String mapType(String typeString) { @@ -247,6 +264,9 @@ static private String getFieldName(Map field) { /** * Helper function to navigate nested maps dynamically. Returns null on any type of error + * @param map Dynamic map from which to extract data + * @param nestedFields ordered array of fields that need to be navigated in the map + * @return value if found, null otherwise (both on casting errors and value not found) */ static private T chainAccess(Map map, String[] nestedFields) { @@ -263,6 +283,9 @@ static private T chainAccess(Map map, String[] nestedFields) { /** * Parametrized helper function to dynamically extract data from a map. Returns null on casting errors + * @param map + * @param key + * @return value if found and cast succeeds, null otherwise */ static private T getValue(Map map, String key) { diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml index 3e3f4fca76..d3952f36f2 100644 --- a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml @@ -5,7 +5,6 @@ - @@ -14,7 +13,6 @@ - diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java index a7f4378aef..52d6768844 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java @@ -16,6 +16,9 @@ public abstract class AbstractJavaTemplateTest { * Model used by freemarker */ public Map model; + /** + * Engine that will generate output from model and template + */ public FreeMarkerTemplateEngine engine; /** * Implementation of TextTemplate for template processing @@ -24,10 +27,10 @@ public abstract class AbstractJavaTemplateTest { /** * Creates an anonymous TextTemplate object that works on the given template files - * * @param relativePath Relative Path to the template from the /templates folder, most likely coincides with template * filename * @param relativeAbsolutePath Relative path from source root to the template + * @return anonymous instance of TextTemplate that holds the data in Overridden interface methods */ public TextTemplate createTemplate(String relativePath, Path relativeAbsolutePath) { @@ -75,15 +78,6 @@ public void addUtil(Class clazz) { } - /** - * Adds object to the model - * @param key - * @param instance - */ - public void addObject(String key, Object instance) { - this.model.put(key, instance); - } - /** * Consumes the given class to produce the template * @param modelClass Class to auto-generate reflective pojo model diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java deleted file mode 100644 index 0a3cff206c..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestAnimal.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest; - -import javax.persistence.Column; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; - -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; - -/** - * This is a parent class for all animals for testing purposes. - * - */ -public class TestAnimal { - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - Long id; - - @Column(name = "ANIMAL_NAME", length = 50, nullable = false) - @Size - @NotNull - private String name; - - public TestAnimal(String name) { - - this.name = name; - } - - /** - * @return name - */ - public String getName() { - - return this.name; - } - - /** - * @param name new value of {@link #getName}. - */ - public void setName(String name) { - - this.name = name; - } - -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java deleted file mode 100644 index e7c078af84..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestCat.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest; - -/** - * This is a child animal class for testing purposes. - * - */ -public class TestCat extends TestAnimal { - - private Integer legs; - - /** - * The constructor. - */ - public TestCat(String name, Integer legs) { - - super(name); - this.legs = legs; - } - - /** - * @return legs - */ - public Integer getLegs() { - - return this.legs; - } - - /** - * @param legs new value of {@link #getLegs}. - */ - public void setLegs(Integer legs) { - - this.legs = legs; - } - -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java deleted file mode 100644 index 989def8e46..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/TestSqlTypeAnnotations.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest; - -import javax.persistence.Column; -import javax.persistence.Id; - -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestAnotherSimpleEntity; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestNotSoSimpleEntity; -import com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities.TestSimpleEntity; - -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; - -/** - * This class is a test class for {@link SQLAnnotationTest} - * - */ -public class TestSqlTypeAnnotations { - - // Specific Statement variations - @Id - Long id; - - String testSimpleString; - - @Size - String testAtSize; - - @Size - Integer testSizeMissing; - - Integer testSimpleInteger; - - @Column(name = "TEST_AT_COLUMN_NULLABLE_AT_NOTNULL", length = 50, nullable = true) - @NotNull - String testAtColumnNullableAtNotNull; - - @Column(name = "TEST_AT_COLUMN_NULLABLE", length = 50, nullable = true) - String testAtColumnNullable; - - @Column(name = "TEST_AT_COLUMN_AT_NOTNULL", length = 50, nullable = false) - @NotNull - String testAtColumnNotNullableAtNotNull; - - @Column(name = "TEST_AT_COLUMN", length = 50, nullable = false) - String testAtColumnNotNullable; - - @NotNull - String testAtNotNull; - - @Size - @NotNull - String testAtSizeAtNotNull; - - @Column(name = "TEST_AT_COLUMN", length = 50, nullable = false) - @Size - @NotNull - TestSimpleEntity testEntityAtColumnNotNullableAtSizeAtNotNull; - - TestSimpleEntity testEntityAtTable; - - TestSimpleEntity testAnonymousEntityAtTable = new TestSimpleEntity("Test", 19) { - @Override - public String testMethod() { - - return "This method will is overwritten"; - } - - }; - - TestAnotherSimpleEntity testEntityAtTableNameDefault; - - TestAnotherSimpleEntity testAnonymousEntityAtTableNameDefault = new TestAnotherSimpleEntity("Test", 19) { - @Override - public String testMethod() { - - return "This method will is overwritten"; - } - - }; - - TestNotSoSimpleEntity testEntityAtTableNull; - - TestNotSoSimpleEntity testAnonymousEntityAtTableNull = new TestNotSoSimpleEntity("Test", 19) { - @Override - public String testMethod() { - - return "This method will is overwritten"; - } - - }; - - @Column(name = "FIELD_AT_COLUMN", length = 50, nullable = false) - Integer testGetColumnNameFieldAtColumn; - - @Column(length = 50, nullable = false) - Integer testGetColumnNameFieldAtColumnBlank; - - Integer testGetColumnNameFieldAtColumnMissing; - - @Column(name = "METHOD_AT_COLUMN", length = 50, nullable = false) - public Integer getTestGetColumnNameMethodAtColumn() { - - return this.testGetColumnNameFieldAtColumn; - } - - @Column(length = 50, nullable = false) - public Integer getTestGetColumnNameMethodAtColumnBlank() { - - return this.testGetColumnNameFieldAtColumnBlank; - } - - public Integer getTestGetColumnNameMethodAtColumnMissing() { - - return this.testGetColumnNameFieldAtColumnMissing; - } -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java deleted file mode 100644 index d3b9cadc21..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestAnotherSimpleEntity.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; - -import jakarta.validation.constraints.NotNull; - -/** - * This is a simple Entity Class for testing. - * - */ - -@Entity -@Table(schema = "RECORDS") -public class TestAnotherSimpleEntity { - - @Id - private Long simpleEntityId; - - @Column(name = "TEST_SIMPLE_NAME", length = 50, nullable = false) - private String name; - - @Column(name = "TEST_SIMPLE_AGE", length = 50) - @NotNull - private Integer age; - - /** - * The constructor. - * - * @param name {@link String} of this test entity - * @param age {@link Integer} of this test entity - */ - public TestAnotherSimpleEntity(String name, @NotNull Integer age) { - - this.name = name; - this.age = age; - } - - /** - * @return string - */ - public String testMethod() { - - return "This method will be overwritten"; - } - -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestNotSoSimpleEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestNotSoSimpleEntity.java deleted file mode 100644 index f364c3a90c..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestNotSoSimpleEntity.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; - -import jakarta.validation.constraints.NotNull; - -/** - * This is a simple Entity Class for testing. - * - */ - -@Entity -public class TestNotSoSimpleEntity { - - @Id - private Long id; - - @Column(name = "TEST_SIMPLE_NAME", length = 50, nullable = false) - private String name; - - @Column(name = "TEST_SIMPLE_AGE", length = 50) - @NotNull - private Integer age; - - /** - * The constructor. - * - * @param name {@link String} of this test entity - * @param age {@link Integer} of this test entity - */ - public TestNotSoSimpleEntity(String name, @NotNull Integer age) { - - this.name = name; - this.age = age; - } - - /** - * @return string - */ - public String testMethod() { - - return "This method will be overwritten"; - } - -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java deleted file mode 100644 index 495bdbb2f9..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/entities/TestSimpleEntity.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.entities; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.OneToOne; -import javax.persistence.Table; - -import jakarta.validation.constraints.NotNull; - -/** - * This is a simple Entity Class for testing. - * - */ - -@Entity -@Table(name = "TEST_SIMPLE_ENTITY", schema = "RECORDS") -public class TestSimpleEntity { - - @Id - private Long id; - - @Column(name = "TEST_SIMPLE_NAME", length = 50, nullable = false) - private String name; - - @Column(name = "TEST_SIMPLE_AGE", length = 50) - @NotNull - private Integer age; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "simpleEntityId") - private TestAnotherSimpleEntity simpleEntity; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn - private TestAnotherSimpleEntity simpleEntityDefaultName; - - /** - * The constructor. - * - * @param name {@link String} of this test entity - * @param age {@link Integer} of this test entity - */ - public TestSimpleEntity(String name, @NotNull Integer age) { - - this.name = name; - this.age = age; - } - - /** - * @return string - */ - public String testMethod() { - - return "This method will be overwritten"; - } - -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/enums/TestSimpleEnum.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/enums/TestSimpleEnum.java deleted file mode 100644 index cc636fcff3..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/enums/TestSimpleEnum.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.enums; - -/** - * This is an enum for sql testing purposes - * - */ -@SuppressWarnings("javadoc") -public enum TestSimpleEnum { - - ONE, TWO, THREE -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java deleted file mode 100644 index f29c335990..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfive.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; - -/** - * TODO leholm This type ... - * - */ -public class Primaryfive { - private Long id; - - public Long getId() { - - return this.id; - } -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfour.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfour.java deleted file mode 100644 index a3086e6109..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryfour.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; - -import javax.persistence.Column; -import javax.persistence.Id; - -/** - * TODO leholm This type ... - * - */ -public class Primaryfour { - private Long testId; - - @Id - @Column(name = "TEST_ID", length = 50, nullable = false) - public Long getTestId() { - - return this.testId; - } -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryone.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryone.java deleted file mode 100644 index e0f2d84d8a..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primaryone.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; - -import javax.persistence.Id; - -/** - * TODO leholm This type ... - * - */ -public class Primaryone { - @Id - Long id; -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarythree.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarythree.java deleted file mode 100644 index d0d8b38ea5..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarythree.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; - -import javax.persistence.Id; - -/** - * TODO leholm This type ... - * - */ -public class Primarythree { - private Long testId; - - @Id - public Long getTestId() { - - return this.testId; - } -} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java deleted file mode 100644 index 150aa7dc73..0000000000 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/resources/sqltest/primarykeys/Primarytwo.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.test.utils.resources.sqltest.primarykeys; - -import javax.persistence.Column; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; - -/** - * TODO leholm This type ... - * - */ -public class Primarytwo { - @Id - @Column(name = "TEST_ID", length = 50, nullable = false) - @GeneratedValue(strategy = GenerationType.AUTO) - private Long id; - -} From 68d01a8494021327fe1a2887776f0e8ec3bca6af Mon Sep 17 00:00:00 2001 From: Lurian Date: Thu, 17 Nov 2022 09:10:20 +0100 Subject: [PATCH 35/39] Removed unneccessary comments and added javadoc to test --- .../devon4j/test/templates/SQLTemplateGenerationTest.java | 3 +++ .../cobigen/templates/devon4j/test/utils/SQLUtilTest.java | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java index fe67048b8c..0c2ae96324 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java @@ -65,6 +65,9 @@ public void testForeignKeyStatements() { assertThat(output).contains("test_id BIGINT, FOREIGN KEY (test_id) REFERENCES SQLTEST(MY_ID_FIELD)"); } + /** + * Tests successful generation of a second CREATE TABLE statement from the @JoinTable annotation + */ @Test public void testJoinTableGeneration() { String output = process(SQLTestJoinTableEntity.class); diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index 3ae02063e1..a40208acc7 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -16,28 +16,24 @@ public class SQLUtilTest { @Test public void testClassTypeMapping() { - // Test fails assertThat(SQLUtil.mapType("Class")).isEqualTo("VARCHAR"); } @Test public void testByteArray() { - // Test fails assertThat(SQLUtil.mapType("byte[]")).isEqualTo("BLOB"); } @Test public void testTimestamp() { - // Test fails assertThat(SQLUtil.mapType("Timestamp")).isEqualTo("TIMESTAMP"); } @Test public void testTimeZone() { - // Test fails assertThat(SQLUtil.mapType("TimeZone")).isEqualTo("VARCHAR"); } From 91e996e876524961614e368571f609e22cf193ee Mon Sep 17 00:00:00 2001 From: Lurian Date: Thu, 17 Nov 2022 10:43:45 +0100 Subject: [PATCH 36/39] added jdoc to getValue() method in SQLUtil --- .../com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index c177668d3b..88d7dc0a7c 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -283,8 +283,8 @@ static private T chainAccess(Map map, String[] nestedFields) { /** * Parametrized helper function to dynamically extract data from a map. Returns null on casting errors - * @param map - * @param key + * @param map Dynamic map from which to extract data + * @param key key for the value * @return value if found and cast succeeds, null otherwise */ static private T getValue(Map map, String key) { From a4b065fb1cb79aa2d4dd36a27c13b007f733ad81 Mon Sep 17 00:00:00 2001 From: Lurian Date: Thu, 17 Nov 2022 12:19:32 +0100 Subject: [PATCH 37/39] Adressed change requests --- .../templates/devon4j/utils/CommonUtil.java | 2 +- .../templates/devon4j/utils/SQLUtil.java | 4 ++- .../templates/AbstractJavaTemplateTest.java | 2 ++ .../testclasses/SQLTestDataTypesEntity.java | 23 +++++++--------- .../devon4j/test/utils/SQLUtilTest.java | 27 +++---------------- 5 files changed, 20 insertions(+), 38 deletions(-) diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java index af5e7ad7a9..147acf64cf 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java @@ -46,7 +46,7 @@ public boolean isEnum(String className) { * @param pojoClass {@link Class} the class object of the pojo * @param fieldName {@link String} the name of the field * @return true if the field is an instance of java.utils.Collections - * @throws NoSuchFieldException indicating something awefully wrong in the used model + * @throws NoSuchFieldException indicating something awfully wrong in the used model * @throws SecurityException if the field cannot be accessed. */ public boolean isCollection(Class pojoClass, String fieldName) throws NoSuchFieldException, SecurityException { diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 88d7dc0a7c..2c266f034c 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -20,7 +20,7 @@ public SQLUtil() { } /** - * Unwraps type to autogenerate a name for the table following devonf naming convention. + * Unwraps type to autogenerate a name for the table following devonfw naming convention. * * @param entityType String that represents the entity class type * @return parsed table name @@ -251,6 +251,8 @@ public static String mapType(String typeString) { /** * Extracts the name of the field from the Map whilst checking for name-override in @Column annotation + * @param field Dynamic map for field + * @return simple name for field as String */ static private String getFieldName(Map field) { diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java index 52d6768844..7f94480974 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java @@ -1,6 +1,7 @@ package com.devonfw.cobigen.templates.devon4j.test.templates; import com.devonfw.cobigen.api.extension.TextTemplate; +import com.devonfw.cobigen.api.extension.TextTemplateEngine; import com.devonfw.cobigen.javaplugin.inputreader.JavaInputReader; import com.devonfw.cobigen.tempeng.freemarker.FreeMarkerTemplateEngine; import org.junit.Before; @@ -53,6 +54,7 @@ public Path getAbsoluteTemplatePath() { * Creates a template engine for the given template path * * @param templateFolderPath Relative path to template folder from project source root + * @return {@link TextTemplateEngine} implementation for Apache FreeMarker. */ public FreeMarkerTemplateEngine createEngine(String templateFolderPath) { diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestDataTypesEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestDataTypesEntity.java index fab85d0204..fcf9d51637 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestDataTypesEntity.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestDataTypesEntity.java @@ -66,8 +66,8 @@ public class SQLTestDataTypesEntity { @Column() private byte[] blob; - @Column() - private Class varchar2; + @Column() + private Class varchar2; @Column() private Locale varchar3; @@ -318,24 +318,21 @@ public void setBlob(byte[] blob) { this.blob = blob; } - - - - /** + /** * @return varchar2 */ - public Class getVarchar2() { + public Class getVarchar2() { - return this.varchar2; - } + return this.varchar2; + } - /** + /** * @param varchar2 new value of {@link #getvarchar2}. */ - public void setVarchar2(Class varchar2) { + public void setVarchar2(Class varchar2) { - this.varchar2 = varchar2; - } + this.varchar2 = varchar2; + } /** * @return varchar3 diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java index a40208acc7..408fd62239 100644 --- a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -13,34 +13,15 @@ */ public class SQLUtilTest { + /** + * Tests type mappings between simple Java types and corresponding SQL types + */ @Test - public void testClassTypeMapping() { - + public void testTypeMappings() { assertThat(SQLUtil.mapType("Class")).isEqualTo("VARCHAR"); - } - - @Test - public void testByteArray() { - assertThat(SQLUtil.mapType("byte[]")).isEqualTo("BLOB"); - } - - @Test - public void testTimestamp() { - assertThat(SQLUtil.mapType("Timestamp")).isEqualTo("TIMESTAMP"); - } - - @Test - public void testTimeZone() { - assertThat(SQLUtil.mapType("TimeZone")).isEqualTo("VARCHAR"); - } - - @Test - public void testCalendar() { - assertThat(SQLUtil.mapType("Calendar")).isEqualTo("TIMESTAMP"); } - } From dbe627daa70bf5e992829184643c1888acc33a64 Mon Sep 17 00:00:00 2001 From: Lurian Date: Fri, 18 Nov 2022 10:39:23 +0100 Subject: [PATCH 38/39] Deleted CommonUtil.java and restored JavaUtil to previous state. --- .../templates/devon4j/utils/CommonUtil.java | 70 ------------------- .../templates/devon4j/utils/JavaUtil.java | 69 +++++++++++++++--- .../templates/devon4j/utils/SQLUtil.java | 2 +- 3 files changed, 60 insertions(+), 81 deletions(-) delete mode 100644 cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java deleted file mode 100644 index 147acf64cf..0000000000 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/CommonUtil.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.devonfw.cobigen.templates.devon4j.utils; - -import java.lang.reflect.Field; -import java.util.Collection; - -import org.apache.commons.lang3.ClassUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This class provides utility for all objects that inherit from it - * - */ -public class CommonUtil { - - /** - * The constructor. - */ - public CommonUtil() { - - // Empty for CobiGen to automatically instantiate it - } - - /** - * Logger for this class - */ - protected static final Logger LOG = LoggerFactory.getLogger(JavaUtil.class); - - /** - * Checks whether the class given by the full qualified name is an enum - * - * @param className full qualified class name - * @return true if the class is an enum, false otherwise - */ - public boolean isEnum(String className) { - - try { - return ClassUtils.getClass(className).isEnum(); - } catch (ClassNotFoundException e) { - LOG.warn("{}: Could not find {}", e.getMessage(), className); - return false; - } - } - - /** - * @param pojoClass {@link Class} the class object of the pojo - * @param fieldName {@link String} the name of the field - * @return true if the field is an instance of java.utils.Collections - * @throws NoSuchFieldException indicating something awfully wrong in the used model - * @throws SecurityException if the field cannot be accessed. - */ - public boolean isCollection(Class pojoClass, String fieldName) throws NoSuchFieldException, SecurityException { - - if (pojoClass == null) { - return false; - } - - Field field = pojoClass.getDeclaredField(fieldName); - if (field == null) { - field = pojoClass.getField(fieldName); - } - if (field == null) { - return false; - } else { - return Collection.class.isAssignableFrom(field.getType()); - } - - } - -} diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java index 943c0bf66b..f1bb367ccc 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java @@ -3,15 +3,23 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Map; import org.apache.commons.lang3.ClassUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Provides type operations, mainly checks and casts for Java Primitives, to be used in the templates * */ -public class JavaUtil extends CommonUtil { +public class JavaUtil { + + /** + * Logger for this class + */ + private static final Logger LOG = LoggerFactory.getLogger(JavaUtil.class); /** * The constructor. @@ -54,7 +62,7 @@ public String boxJavaPrimitives(Class pojoClass, String fieldName) throws NoS if (pojoClass == null) { throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } if (equalsJavaPrimitive(pojoClass, fieldName)) { @@ -114,7 +122,7 @@ public boolean equalsJavaPrimitiveOrWrapper(String simpleType) { * @throws SecurityException if the field cannot be accessed. */ public boolean equalsJavaPrimitive(Class pojoClass, String fieldName) - throws NoSuchFieldException, SecurityException { + throws NoSuchFieldException, SecurityException { if (pojoClass == null) { return false; @@ -161,10 +169,10 @@ public boolean equalsJavaPrimitiveIncludingArrays(String simpleType) { * @throws SecurityException if the field cannot be accessed. */ public boolean equalsJavaPrimitiveIncludingArrays(Class pojoClass, String fieldName) - throws NoSuchFieldException, SecurityException { + throws NoSuchFieldException, SecurityException { return equalsJavaPrimitive(pojoClass, fieldName) || (pojoClass.getDeclaredField(fieldName).getType().isArray() - && pojoClass.getDeclaredField(fieldName).getType().getComponentType().isPrimitive()); + && pojoClass.getDeclaredField(fieldName).getType().getComponentType().isPrimitive()); } /** @@ -198,7 +206,7 @@ public String castJavaPrimitives(String simpleType, String varName) throws Class * @throws SecurityException if the field cannot be accessed. */ public String castJavaPrimitives(Class pojoClass, String fieldName) - throws NoSuchFieldException, SecurityException { + throws NoSuchFieldException, SecurityException { if (equalsJavaPrimitive(pojoClass, fieldName)) { return String.format("((%1$s)%2$s)", boxJavaPrimitives(pojoClass, fieldName), fieldName); @@ -207,6 +215,31 @@ public String castJavaPrimitives(Class pojoClass, String fieldName) } } + /** + * @param pojoClass {@link Class} the class object of the pojo + * @param fieldName {@link String} the name of the field + * @return true if the field is an instance of java.utils.Collections + * @throws NoSuchFieldException indicating something awefully wrong in the used model + * @throws SecurityException if the field cannot be accessed. + */ + public boolean isCollection(Class pojoClass, String fieldName) throws NoSuchFieldException, SecurityException { + + if (pojoClass == null) { + return false; + } + + Field field = pojoClass.getDeclaredField(fieldName); + if (field == null) { + field = pojoClass.getField(fieldName); + } + if (field == null) { + return false; + } else { + return Collection.class.isAssignableFrom(field.getType()); + } + + } + /** * Returns the Ext Type to a given java type * @@ -301,7 +334,7 @@ public String getReturnType(Class pojoClass, String methodName) throws NoSuch if (pojoClass == null) { throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } String s = "-"; Method method = findMethod(pojoClass, methodName); @@ -324,11 +357,11 @@ public String getReturnType(Class pojoClass, String methodName) throws NoSuch */ @SuppressWarnings("unchecked") public String getReturnTypeOfMethodAnnotatedWith(Class pojoClass, String annotatedClassName) - throws ClassNotFoundException { + throws ClassNotFoundException { if (pojoClass == null) { throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } Method[] methods = pojoClass.getDeclaredMethods(); @@ -381,7 +414,7 @@ private Method findMethod(Class pojoClass, String methodName) { if (pojoClass == null) { throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } for (Method m : pojoClass.getMethods()) { if (m.getName().equals(methodName)) { @@ -391,6 +424,22 @@ private Method findMethod(Class pojoClass, String methodName) { return null; } + /** + * Checks whether the class given by the full qualified name is an enum + * + * @param className full qualified class name + * @return true if the class is an enum, false otherwise + */ + public boolean isEnum(String className) { + + try { + return ClassUtils.getClass(className).isEnum(); + } catch (ClassNotFoundException e) { + LOG.warn("{}: Could not find {}", e.getMessage(), className); + return false; + } + } + /** * Returns the first enum value of an enum class * diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java index 2c266f034c..2795d835b1 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -7,7 +7,7 @@ * Provides operations to identify and process SQL specific information * */ -public class SQLUtil extends CommonUtil { +public class SQLUtil { private static int DEFAULT_FIELD_LENGTH = 255; From ddc8b7e947db6c5715ffbc27572fc98999ca5aa1 Mon Sep 17 00:00:00 2001 From: "CORP\\leholm" Date: Thu, 16 Feb 2023 14:39:45 +0100 Subject: [PATCH 39/39] no message --- .../unittest/ExternalProcess/DummyExe.exe | Bin 122368 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 cobigen/cobigen-core/src/test/resources/testdata/unittest/ExternalProcess/DummyExe.exe diff --git a/cobigen/cobigen-core/src/test/resources/testdata/unittest/ExternalProcess/DummyExe.exe b/cobigen/cobigen-core/src/test/resources/testdata/unittest/ExternalProcess/DummyExe.exe deleted file mode 100644 index 4612730a845d7b89160965896948482bcdd7bfe4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122368 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

PN5`?fo~hH~p2{9CdKNLZ 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