You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/07/20 16:39:06 UTC

(cayenne) branch master updated (882677e35 -> 6f0cafcaf)

This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git


    from 882677e35 CAY-2980 Improve model name generation
     new 67a22bd15 CAY-2980 Improve model name generation
     new 56959de5d cleanup
     new e33e741ec CAY-2980 Improve model name generation
     new 516de7900 cleanup
     new 6f0cafcaf Fixing missed validations

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 RELEASE-NOTES.txt                                  |  1 +
 ai-plugin/references/model-naming-conventions.md   | 11 +++-
 ai-plugin/skills/cayenne-model-naming/SKILL.md     |  9 ++-
 cayenne-cgen/pom.xml                               |  2 +-
 .../java/org/apache/cayenne/gen/StringUtils.java   |  4 +-
 .../dbsync/naming/BaseObjectNameGenerator.java     | 23 ++++++-
 .../naming/DefaultObjectNameGeneratorTest.java     | 28 +++++++++
 .../project/validation/DataMapValidator.java       |  7 ++-
 .../project/validation/DbAttributeValidator.java   |  3 +-
 .../validation/DbRelationshipValidator.java        |  3 +-
 ...ameValidationHelper.java => NameValidator.java} | 67 ++++++++++----------
 .../project/validation/ObjAttributeValidator.java  |  5 +-
 .../project/validation/ObjEntityValidator.java     | 16 ++---
 .../validation/ObjRelationshipValidator.java       |  5 +-
 .../project/validation/NameValidatorTest.java      | 72 ++++++++++++++++++++++
 15 files changed, 191 insertions(+), 65 deletions(-)
 rename cayenne-project/src/main/java/org/apache/cayenne/project/validation/{NameValidationHelper.java => NameValidator.java} (70%)
 create mode 100644 cayenne-project/src/test/java/org/apache/cayenne/project/validation/NameValidatorTest.java


(cayenne) 02/05: cleanup

Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 56959de5dbde0658ffa4ebc99cc9fa391be0a0cd
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Mon Jul 20 18:17:52 2026 +0200

    cleanup
---
 .../org/apache/cayenne/project/validation/NameValidationHelper.java | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java
index 596efa8b3..c46b345d7 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java
@@ -30,7 +30,7 @@ import java.util.StringTokenizer;
  */
 public class NameValidationHelper {
 
-    static final Collection<String> RESERVED_JAVA_KEYWORDS = List.of(
+    private static final Collection<String> RESERVED_JAVA_KEYWORDS = List.of(
             "abstract",
             "assert",
             "default",
@@ -88,7 +88,7 @@ public class NameValidationHelper {
 
     // a property is considered invalid if there is a getter or a setter for it in
     // java.lang.Object or PersistentObject
-    static final Collection<String> INVALID_JAVA_PROPERTIES = List.of(
+    private static final Collection<String> PERSISTENT_BASE_PROPERTIES = List.of(
             "class",
             "committedSnapshot",
             "currentSnapshot",
@@ -186,6 +186,6 @@ public class NameValidationHelper {
      */
     public boolean invalidPersistentObjectProperty(String persistentObjectProperty) {
         return persistentObjectProperty == null
-                || INVALID_JAVA_PROPERTIES.contains(persistentObjectProperty);
+                || PERSISTENT_BASE_PROPERTIES.contains(persistentObjectProperty);
     }
 }


(cayenne) 04/05: cleanup

Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 516de7900f143b3f229a12fe2f336302147a33f0
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Mon Jul 20 18:31:10 2026 +0200

    cleanup
---
 cayenne-cgen/pom.xml                               |  2 +-
 .../java/org/apache/cayenne/gen/StringUtils.java   |  4 +--
 .../dbsync/naming/BaseObjectNameGenerator.java     |  4 +--
 .../project/validation/DataMapValidator.java       |  3 +-
 .../project/validation/DbAttributeValidator.java   |  3 +-
 .../validation/DbRelationshipValidator.java        |  3 +-
 ...ameValidationHelper.java => NameValidator.java} | 32 +++++++++-------------
 .../project/validation/ObjAttributeValidator.java  |  5 ++--
 .../project/validation/ObjEntityValidator.java     | 10 +++----
 .../validation/ObjRelationshipValidator.java       |  5 ++--
 10 files changed, 29 insertions(+), 42 deletions(-)

diff --git a/cayenne-cgen/pom.xml b/cayenne-cgen/pom.xml
index c242e0fb5..b926c79cc 100644
--- a/cayenne-cgen/pom.xml
+++ b/cayenne-cgen/pom.xml
@@ -32,7 +32,7 @@
 
     <dependencies>
         <!--
-        using org.apache.cayenne.project.validation.NameValidationHelper from cayenne-project
+        using org.apache.cayenne.project.validation.NameValidator from cayenne-project
         and org.apache.cayenne.dbsync.filter.NameFilter from cayenne-dbsync
         -->
         <dependency>
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/StringUtils.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/StringUtils.java
index d9a2277c6..ee281582b 100644
--- a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/StringUtils.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/StringUtils.java
@@ -19,7 +19,7 @@
 
 package org.apache.cayenne.gen;
 
-import org.apache.cayenne.project.validation.NameValidationHelper;
+import org.apache.cayenne.project.validation.NameValidator;
 import org.apache.cayenne.util.Util;
 
 /**
@@ -42,7 +42,7 @@ public class StringUtils {
      * keywords.
      */
     public String formatVariableName(String variableName) {
-        if (NameValidationHelper.getInstance().isReservedJavaKeyword(variableName)) {
+        if (NameValidator.isReservedJavaKeyword(variableName)) {
             return "_" + variableName;
         }
         else {
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/BaseObjectNameGenerator.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/BaseObjectNameGenerator.java
index b0af9b546..d60aa4db0 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/BaseObjectNameGenerator.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/BaseObjectNameGenerator.java
@@ -22,7 +22,7 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.project.validation.NameValidationHelper;
+import org.apache.cayenne.project.validation.NameValidator;
 import org.apache.cayenne.util.Util;
 
 import java.util.List;
@@ -87,7 +87,7 @@ public abstract class BaseObjectNameGenerator implements ObjectNameGenerator {
      */
     private String fixPersistentBaseProperty(String name, String entityName) {
 
-        if (entityName == null || !NameValidationHelper.getInstance().invalidPersistentObjectProperty(name)) {
+        if (entityName == null || !NameValidator.invalidPersistentProperty(name)) {
             return name;
         }
 
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DataMapValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DataMapValidator.java
index bc78578a2..c4931a8ae 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DataMapValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DataMapValidator.java
@@ -99,8 +99,7 @@ class DataMapValidator extends ConfigurationNodeValidator<DataMap> {
             return;
         }
 
-        NameValidationHelper helper = NameValidationHelper.getInstance();
-        String invalidChars = helper.invalidCharsInJavaClassName(javaPackage);
+        String invalidChars = NameValidator.invalidCharsInJavaClassName(javaPackage);
         if (invalidChars != null) {
             addFailure(validationResult, map, "DataMap '%s' Java package '%s' contains invalid characters: %s",
                     map.getName(), javaPackage, invalidChars);
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbAttributeValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbAttributeValidator.java
index 946c5e796..1f0d6bb0b 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbAttributeValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbAttributeValidator.java
@@ -52,9 +52,8 @@ class DbAttributeValidator extends ConfigurationNodeValidator<DbAttribute> {
     }
 
     private void validateName(DbAttribute attribute, ValidationResult validationResult) {
-        NameValidationHelper helper = NameValidationHelper.getInstance();
         String name = attribute.getName();
-        String invalidChars = helper.invalidCharsInDbPathComponent(name);
+        String invalidChars = NameValidator.invalidCharsInDbPathComponent(name);
         if (Util.isEmptyString(name)) {
             return;
         }
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbRelationshipValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbRelationshipValidator.java
index a56fc2fd7..c723fea8c 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbRelationshipValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbRelationshipValidator.java
@@ -74,8 +74,7 @@ class DbRelationshipValidator extends ConfigurationNodeValidator<DbRelationship>
     }
 
     private void validateName(DbRelationship relationship, ValidationResult validationResult) {
-        NameValidationHelper helper = NameValidationHelper.getInstance();
-        String invalidChars = helper.invalidCharsInDbPathComponent(relationship.getName());
+        String invalidChars = NameValidator.invalidCharsInDbPathComponent(relationship.getName());
         if (invalidChars != null) {
             addFailure(validationResult, relationship, "Name of DbRelationship '%s' contains invalid characters: %s",
                     toString(relationship), invalidChars);
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidator.java
similarity index 84%
rename from cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java
rename to cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidator.java
index 427235815..581e7c5e1 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidator.java
@@ -25,10 +25,10 @@ import java.util.StringTokenizer;
 
 /**
  * Defines a set of rules for validating java and db mapping identifiers.
- * 
- * @since 1.1
+ *
+ * @since 5.0
  */
-public class NameValidationHelper {
+public class NameValidator {
 
     private static final Collection<String> RESERVED_JAVA_KEYWORDS = List.of(
             "abstract",
@@ -90,16 +90,10 @@ public class NameValidationHelper {
             "persistenceState",
             "snapshotVersion");
 
-    static final NameValidationHelper sharedInstance = new NameValidationHelper();
-
-    /**
-     * Returns shared instance of the validator.
-     */
-    public static NameValidationHelper getInstance() {
-        return sharedInstance;
+    private NameValidator() {
     }
 
-    public boolean isReservedJavaKeyword(String word) {
+    public static boolean isReservedJavaKeyword(String word) {
         return RESERVED_JAVA_KEYWORDS.contains(word);
     }
 
@@ -107,19 +101,19 @@ public class NameValidationHelper {
      * This is more of a sanity check than a real validation. As different DBs allow
      * different chars in identifiers, here we simply check for dots.
      */
-    public String invalidCharsInDbPathComponent(String dbPathComponent) {
+    public static String invalidCharsInDbPathComponent(String dbPathComponent) {
         return (dbPathComponent.indexOf('.') >= 0) ? "." : null;
     }
 
     /**
      * Scans a name of ObjAttribute or ObjRelationship for invalid characters.
      */
-    public String invalidCharsInObjPathComponent(String objPathComponent) {
+    public static String invalidCharsInObjPathComponent(String objPathComponent) {
         String invalidChars = validateJavaIdentifier(objPathComponent, "");
         return (!invalidChars.isEmpty()) ? invalidChars : null;
     }
 
-    public String invalidCharsInJavaClassName(String javaClassName) {
+    public static String invalidCharsInJavaClassName(String javaClassName) {
         if (javaClassName == null) {
             return null;
         }
@@ -134,12 +128,12 @@ public class NameValidationHelper {
         return !invalidChars.isEmpty() ? invalidChars : null;
     }
 
-    public boolean invalidPersistentObjectClass(String persistentObjectClassFQN) {
-        if (persistentObjectClassFQN == null) {
+    public static boolean invalidJavaClassComponents(String classFQN) {
+        if (classFQN == null) {
             return true;
         }
 
-        StringTokenizer toks = new StringTokenizer(persistentObjectClassFQN, ".");
+        StringTokenizer toks = new StringTokenizer(classFQN, ".");
         while (toks.hasMoreTokens()) {
             if (RESERVED_JAVA_KEYWORDS.contains(toks.nextToken())) {
                 return true;
@@ -149,7 +143,7 @@ public class NameValidationHelper {
         return false;
     }
 
-    private String validateJavaIdentifier(String id, String invalidChars) {
+    private static String validateJavaIdentifier(String id, String invalidChars) {
         // TODO: Java spec seems to allow "$" char in identifiers...
         // Cayenne expressions do not, so we should probably check for this char presence...
 
@@ -183,7 +177,7 @@ public class NameValidationHelper {
      * considered invalid if there is a getter or a setter for it in java.lang.Object or
      * PersistentObject.
      */
-    public boolean invalidPersistentObjectProperty(String persistentObjectProperty) {
+    public static boolean invalidPersistentProperty(String persistentObjectProperty) {
         return persistentObjectProperty == null
                 || PERSISTENT_BASE_PROPERTIES.contains(persistentObjectProperty);
     }
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjAttributeValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjAttributeValidator.java
index 9db14bd42..675de643f 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjAttributeValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjAttributeValidator.java
@@ -59,15 +59,14 @@ class ObjAttributeValidator extends ConfigurationNodeValidator<ObjAttribute> {
     }
 
     private void validateName(ObjAttribute attribute, ValidationResult validationResult) {
-        NameValidationHelper helper = NameValidationHelper.getInstance();
-        String invalidChars = helper.invalidCharsInObjPathComponent(attribute.getName());
+        String invalidChars = NameValidator.invalidCharsInObjPathComponent(attribute.getName());
 
         if (invalidChars != null) {
             addFailure(validationResult, attribute,
                     "ObjAttribute name '%s' contains invalid characters: %s",
                     attribute.getName(),
                     invalidChars);
-        } else if (helper.invalidPersistentObjectProperty(attribute.getName())) {
+        } else if (NameValidator.invalidPersistentProperty(attribute.getName())) {
             addFailure(validationResult, attribute,
                     "ObjAttribute name '%s' is invalid",
                     attribute.getName());
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjEntityValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjEntityValidator.java
index b44445241..2b33de354 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjEntityValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjEntityValidator.java
@@ -109,13 +109,12 @@ class ObjEntityValidator extends ConfigurationNodeValidator<ObjEntity> {
             return;
         }
 
-        NameValidationHelper helper = NameValidationHelper.getInstance();
-        String invalidChars = helper.invalidCharsInJavaClassName(className);
+        String invalidChars = NameValidator.invalidCharsInJavaClassName(className);
 
         if (invalidChars != null) {
             addFailure(validationResult, entity, "ObjEntity '%s' Java class '%s' contains invalid characters: %s",
                     entity.getName(), className, invalidChars);
-        } else if (helper.invalidPersistentObjectClass(className)) {
+        } else if (NameValidator.invalidJavaClassComponents(className)) {
             addFailure(validationResult, entity, "Java class '%s' of ObjEntity '%s' is a reserved word",
                     className, entity.getName());
         } else if (className.indexOf('.') < 0) {
@@ -131,13 +130,12 @@ class ObjEntityValidator extends ConfigurationNodeValidator<ObjEntity> {
             return; // null is Ok
         }
 
-        NameValidationHelper helper = NameValidationHelper.getInstance();
-        String invalidChars = helper.invalidCharsInJavaClassName(superClassName);
+        String invalidChars = NameValidator.invalidCharsInJavaClassName(superClassName);
 
         if (invalidChars != null) {
             addFailure(validationResult, entity, "ObjEntity '%s' Java superclass '%s' contains invalid characters: %s",
                     entity.getName(), superClassName, invalidChars);
-        } else if (helper.invalidPersistentObjectClass(superClassName)) {
+        } else if (NameValidator.invalidJavaClassComponents(superClassName)) {
             addFailure(validationResult, entity, "ObjEntity '%s' Java superclass '%s' is a reserved word",
                     entity.getName(), superClassName);
         }
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjRelationshipValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjRelationshipValidator.java
index 25cebcb5a..ceb13d127 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjRelationshipValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjRelationshipValidator.java
@@ -71,13 +71,12 @@ class ObjRelationshipValidator extends ConfigurationNodeValidator<ObjRelationshi
     }
 
     private void validateName(ObjRelationship relationship, ValidationResult validationResult) {
-        NameValidationHelper helper = NameValidationHelper.getInstance();
-        String invalidChars = helper.invalidCharsInObjPathComponent(relationship.getName());
+        String invalidChars = NameValidator.invalidCharsInObjPathComponent(relationship.getName());
 
         if (invalidChars != null) {
             addFailure(validationResult, relationship, "ObjRelationship name '%s' contains invalid characters: %s",
                     toString(relationship), invalidChars);
-        } else if (helper.invalidPersistentObjectProperty(relationship.getName())) {
+        } else if (NameValidator.invalidPersistentProperty(relationship.getName())) {
             addFailure(validationResult, relationship, "ObjRelationship name '%s' is a reserved word",
                     toString(relationship));
         }


(cayenne) 03/05: CAY-2980 Improve model name generation

Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit e33e741ec3291337ddb686f4c8bfa562d0c9c587
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Mon Jul 20 15:52:21 2026 +0200

    CAY-2980 Improve model name generation
    
    auto-renaming properties conflicting with Persistent object
---
 RELEASE-NOTES.txt                                  |  1 +
 ai-plugin/references/model-naming-conventions.md   | 14 +++++++----
 ai-plugin/skills/cayenne-model-naming/SKILL.md     | 11 +++++----
 .../dbsync/naming/BaseObjectNameGenerator.java     | 23 ++++++++++++++++--
 .../naming/DefaultObjectNameGeneratorTest.java     | 28 ++++++++++++++++++++++
 .../project/validation/NameValidationHelper.java   | 25 ++++++++++---------
 6 files changed, 77 insertions(+), 25 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 1dcfd71c4..dfdb33ca7 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -27,6 +27,7 @@ CAY-2974 CayenneSqlException with a reference to translated query
 CAY-2975 Mnemonic table aliases in generated SQL
 CAY-2978 AI skill: "cayenne-model-naming"
 CAY-2979 AI skill: "cayenne-full-db-sync"
+CAY-2980 Improve model name generation
 
 Bug Fixes:
 
diff --git a/ai-plugin/references/model-naming-conventions.md b/ai-plugin/references/model-naming-conventions.md
index 32aec7471..c65b83005 100644
--- a/ai-plugin/references/model-naming-conventions.md
+++ b/ai-plugin/references/model-naming-conventions.md
@@ -143,11 +143,15 @@ The three cases above don't exhaust the ways a purely mechanical transliteration
 you spot a name where a human reading the underlying DB name would obviously do better, and the fix
 is defensible (not a guess), apply the same conservative treatment. Some more examples:
 
-- **Genuinely illegal identifiers** the generator passed through — a name starting with a digit, or
-  literally `class`, whose getter `getClass()` collides with the final `Object.getClass()`. Java
-  *keywords* are **not** a problem — `default`, `package`, `return` and the like compile fine, since
-  class generation prefixes the field/parameter name with `_` and embeds the capitalized name in the
-  accessors (`getDefault()` / `setDefault()`). Leave keyword-named properties alone.
+- **Genuinely illegal identifiers** the generator passed through — chiefly a name starting with a
+  digit. Java *keywords* are **not** a problem — `default`, `package`, `return` and the like compile
+  fine, since class generation prefixes the field/parameter name with `_` and embeds the capitalized
+  name in the accessors (`getDefault()` / `setDefault()`); leave keyword-named properties alone.
+  Property names clashing with base-class getters (`class`, `objectId`, `objectContext`,
+  `persistenceState`, `snapshotVersion` — e.g. `getClass()` collides with the final
+  `Object.getClass()`) are qualified with the entity name by the generator itself (`class` on
+  `student` → `studentClass`) — already handled; models imported by older Cayenne versions may still
+  carry them raw, and those do need the same entity-qualified rename.
 - **Lost acronym casing** — `HTTPURL` → `Gametype`-style collapse loses the acronym; `httpUrl` /
   `url` may read better than `httpurl`.
 - **Plural table → singular entity** — a `CUSTOMERS` table yields `Customers`; an entity is a single
diff --git a/ai-plugin/skills/cayenne-model-naming/SKILL.md b/ai-plugin/skills/cayenne-model-naming/SKILL.md
index 44b1af552..fb26687d9 100644
--- a/ai-plugin/skills/cayenne-model-naming/SKILL.md
+++ b/ai-plugin/skills/cayenne-model-naming/SKILL.md
@@ -95,11 +95,12 @@ correct — leave them.** Flag only the cases the deterministic generator can't
    names (`customer`, `orders`); a relationship is a role/property, and the prefix is noise there.
    **Leave the entity names alone** — the prefix on classes is the user's choice, and renaming
    entities regenerates classes.
-4. **Other clear, defensible improvements** — illegal identifiers (digit-leading names, or `class`,
-   whose getter would clash with `Object.getClass()` — Java keywords are fine, cgen escapes them),
-   lost acronym casing, obvious cryptic abbreviations applied consistently,
-   plural-table-to-singular-entity. Conservative by default; when unsure, leave the baseline name
-   and ask.
+4. **Other clear, defensible improvements** — illegal identifiers (digit-leading names; Java
+   keywords are fine — cgen escapes them — and properties clashing with base-class getters like
+   `class` / `objectId` are auto-qualified with the entity name by the generator, though older
+   imports may carry them raw), lost acronym casing, obvious cryptic abbreviations applied
+   consistently, plural-table-to-singular-entity. Conservative by default; when unsure, leave the
+   baseline name and ask.
 
 Relationship cleanup is anchored on the **DbRelationship** — it's the first-class citizen, since
 every FK has one whether or not an ObjRelationship was generated on top. The rules above (run-together,
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/BaseObjectNameGenerator.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/BaseObjectNameGenerator.java
index 26d230ade..b0af9b546 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/BaseObjectNameGenerator.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/BaseObjectNameGenerator.java
@@ -22,6 +22,7 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.project.validation.NameValidationHelper;
 import org.apache.cayenne.util.Util;
 
 import java.util.List;
@@ -43,7 +44,9 @@ public abstract class BaseObjectNameGenerator implements ObjectNameGenerator {
 
     @Override
     public String objAttributeName(DbAttribute dbAttribute) {
-        return Util.underscoredToJava(dbAttribute.getName(), false);
+        String name = Util.underscoredToJava(dbAttribute.getName(), false);
+        String entityName = dbAttribute.getEntity() != null ? dbAttribute.getEntity().getName() : null;
+        return fixPersistentBaseProperty(name, entityName);
     }
 
     @Override
@@ -72,7 +75,23 @@ public abstract class BaseObjectNameGenerator implements ObjectNameGenerator {
         String baseName = toMany
                 ? toManyBase(joins, targetEntityName)
                 : toOneBase(joins, targetEntityName);
-        return Util.underscoredToJava(baseName, false);
+        String name = Util.underscoredToJava(baseName, false);
+
+        DbEntity sourceEntity = joins.getFirst().getRelationship().getSourceEntity();
+        return fixPersistentBaseProperty(name, sourceEntity != null ? sourceEntity.getName() : null);
+    }
+
+    /**
+     * Qualifies a property name that has a getter in Object or PersistentObject with the entity name, the way
+     * a human would ("class" on "student" becomes "studentClass") — such a name can't be generated as-is.
+     */
+    private String fixPersistentBaseProperty(String name, String entityName) {
+
+        if (entityName == null || !NameValidationHelper.getInstance().invalidPersistentObjectProperty(name)) {
+            return name;
+        }
+
+        return Util.underscoredToJava(dbEntityBaseName(entityName) + "_" + name, false);
     }
 
     protected boolean isToMany(DbRelationship... relationshipChain) {
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/DefaultObjectNameGeneratorTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/DefaultObjectNameGeneratorTest.java
index 214573df7..35a24568d 100644
--- a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/DefaultObjectNameGeneratorTest.java
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/DefaultObjectNameGeneratorTest.java
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.cayenne.dbsync.naming;
 
+import java.sql.Types;
+
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
@@ -260,4 +262,30 @@ public class DefaultObjectNameGeneratorTest {
         assertEquals("name", generator.objAttributeName(new DbAttribute("NAME")));
         assertEquals("artistName", generator.objAttributeName(new DbAttribute("ARTIST_NAME")));
     }
+
+    @Test
+    public void objAttributeName_PersistentBaseProperty() {
+
+        // names with a getter in Object or PersistentObject are qualified with the entity name
+        DbEntity student = new DbEntity("STUDENT");
+        assertEquals("studentClass", generator.objAttributeName(new DbAttribute("CLASS", Types.VARCHAR, student)));
+        assertEquals("studentObjectId",
+                generator.objAttributeName(new DbAttribute("OBJECT_ID", Types.INTEGER, student)));
+        assertEquals("studentSnapshotVersion",
+                generator.objAttributeName(new DbAttribute("SNAPSHOT_VERSION", Types.INTEGER, student)));
+
+        // ordinary names are unaffected
+        assertEquals("classRoom", generator.objAttributeName(new DbAttribute("CLASS_ROOM", Types.VARCHAR, student)));
+    }
+
+    @Test
+    public void dbRelationshipName_ToOne_PersistentBaseProperty() {
+
+        // an FK-derived name with a getter in Object or PersistentObject is qualified with the source entity name
+        DbRelationship r1 = makeRelationship("student", "class_id", "class", "id", false);
+        assertEquals("studentClass", dbRelationshipName(r1));
+
+        DbRelationship r2 = makeRelationship("AUDIT", "OBJECT_CONTEXT_ID", "CONTEXT", "ID", false);
+        assertEquals("auditObjectContext", dbRelationshipName(r2));
+    }
 }
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java
index c46b345d7..427235815 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidationHelper.java
@@ -82,18 +82,11 @@ public class NameValidationHelper {
             "package",
             "synchronized");
 
-    public boolean isReservedJavaKeyword(String word) {
-        return RESERVED_JAVA_KEYWORDS.contains(word);
-    }
-
-    // a property is considered invalid if there is a getter or a setter for it in
-    // java.lang.Object or PersistentObject
+    // property getter or setter would conflict with Object or Persistent
     private static final Collection<String> PERSISTENT_BASE_PROPERTIES = List.of(
             "class",
-            "committedSnapshot",
-            "currentSnapshot",
-            "dataContext",
             "objectId",
+            "objectContext",
             "persistenceState",
             "snapshotVersion");
 
@@ -106,6 +99,10 @@ public class NameValidationHelper {
         return sharedInstance;
     }
 
+    public boolean isReservedJavaKeyword(String word) {
+        return RESERVED_JAVA_KEYWORDS.contains(word);
+    }
+
     /**
      * This is more of a sanity check than a real validation. As different DBs allow
      * different chars in identifiers, here we simply check for dots.
@@ -119,7 +116,7 @@ public class NameValidationHelper {
      */
     public String invalidCharsInObjPathComponent(String objPathComponent) {
         String invalidChars = validateJavaIdentifier(objPathComponent, "");
-        return (invalidChars.length() > 0) ? invalidChars : null;
+        return (!invalidChars.isEmpty()) ? invalidChars : null;
     }
 
     public String invalidCharsInJavaClassName(String javaClassName) {
@@ -134,7 +131,7 @@ public class NameValidationHelper {
             invalidChars = validateJavaIdentifier(toks.nextToken(), invalidChars);
         }
 
-        return (invalidChars.length() > 0) ? invalidChars : null;
+        return !invalidChars.isEmpty() ? invalidChars : null;
     }
 
     public boolean invalidPersistentObjectClass(String persistentObjectClassFQN) {
@@ -167,14 +164,16 @@ public class NameValidationHelper {
             }
         }
 
+        StringBuilder buf = new StringBuilder(invalidChars);
         for (int i = 1; i < len; i++) {
 
             if (!Character.isJavaIdentifierPart(id.charAt(i))) {
-                if (invalidChars.indexOf(id.charAt(i)) < 0) {
-                    invalidChars = invalidChars + id.charAt(i);
+                if (buf.toString().indexOf(id.charAt(i)) < 0) {
+                    buf.append(id.charAt(i));
                 }
             }
         }
+        invalidChars = buf.toString();
 
         return invalidChars;
     }


(cayenne) 05/05: Fixing missed validations

Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 6f0cafcaff9aa9ad2a8d9df6598d5725dcaa8838
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Mon Jul 20 18:31:10 2026 +0200

    Fixing missed validations
---
 .../project/validation/DataMapValidator.java       |  4 ++
 .../cayenne/project/validation/NameValidator.java  | 10 ++-
 .../project/validation/ObjEntityValidator.java     |  6 +-
 .../project/validation/NameValidatorTest.java      | 72 ++++++++++++++++++++++
 4 files changed, 87 insertions(+), 5 deletions(-)

diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DataMapValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DataMapValidator.java
index c4931a8ae..4b617c777 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DataMapValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DataMapValidator.java
@@ -103,6 +103,10 @@ class DataMapValidator extends ConfigurationNodeValidator<DataMap> {
         if (invalidChars != null) {
             addFailure(validationResult, map, "DataMap '%s' Java package '%s' contains invalid characters: %s",
                     map.getName(), javaPackage, invalidChars);
+        } else if (NameValidator.invalidJavaClassComponents(javaPackage)) {
+            addFailure(validationResult, map,
+                    "DataMap '%s' Java package '%s' contains a reserved word or an empty name component",
+                    map.getName(), javaPackage);
         }
     }
 }
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidator.java
index 581e7c5e1..706ad6c46 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/NameValidator.java
@@ -128,14 +128,18 @@ public class NameValidator {
         return !invalidChars.isEmpty() ? invalidChars : null;
     }
 
+    /**
+     * Returns whether a "."-separated class or package name contains invalid components: reserved Java
+     * keywords or empty segments ("com.default.Foo", "com..Foo", ".Foo", "Foo.").
+     */
     public static boolean invalidJavaClassComponents(String classFQN) {
         if (classFQN == null) {
             return true;
         }
 
-        StringTokenizer toks = new StringTokenizer(classFQN, ".");
-        while (toks.hasMoreTokens()) {
-            if (RESERVED_JAVA_KEYWORDS.contains(toks.nextToken())) {
+        // the -1 limit keeps trailing empty segments that StringTokenizer would silently skip
+        for (String component : classFQN.split("\\.", -1)) {
+            if (component.isEmpty() || RESERVED_JAVA_KEYWORDS.contains(component)) {
                 return true;
             }
         }
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjEntityValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjEntityValidator.java
index 2b33de354..b08fa2647 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjEntityValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ObjEntityValidator.java
@@ -115,7 +115,8 @@ class ObjEntityValidator extends ConfigurationNodeValidator<ObjEntity> {
             addFailure(validationResult, entity, "ObjEntity '%s' Java class '%s' contains invalid characters: %s",
                     entity.getName(), className, invalidChars);
         } else if (NameValidator.invalidJavaClassComponents(className)) {
-            addFailure(validationResult, entity, "Java class '%s' of ObjEntity '%s' is a reserved word",
+            addFailure(validationResult, entity,
+                    "Java class '%s' of ObjEntity '%s' contains a reserved word or an empty name component",
                     className, entity.getName());
         } else if (className.indexOf('.') < 0) {
             addFailure(validationResult, entity, "Java class '%s' of ObjEntity '%s' is in a default package",
@@ -136,7 +137,8 @@ class ObjEntityValidator extends ConfigurationNodeValidator<ObjEntity> {
             addFailure(validationResult, entity, "ObjEntity '%s' Java superclass '%s' contains invalid characters: %s",
                     entity.getName(), superClassName, invalidChars);
         } else if (NameValidator.invalidJavaClassComponents(superClassName)) {
-            addFailure(validationResult, entity, "ObjEntity '%s' Java superclass '%s' is a reserved word",
+            addFailure(validationResult, entity,
+                    "ObjEntity '%s' Java superclass '%s' contains a reserved word or an empty name component",
                     entity.getName(), superClassName);
         }
 
diff --git a/cayenne-project/src/test/java/org/apache/cayenne/project/validation/NameValidatorTest.java b/cayenne-project/src/test/java/org/apache/cayenne/project/validation/NameValidatorTest.java
new file mode 100644
index 000000000..e58d0df1a
--- /dev/null
+++ b/cayenne-project/src/test/java/org/apache/cayenne/project/validation/NameValidatorTest.java
@@ -0,0 +1,72 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.project.validation;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class NameValidatorTest {
+
+    @Test
+    public void invalidJavaClassComponents_Valid() {
+        assertFalse(NameValidator.invalidJavaClassComponents("Artist"));
+        assertFalse(NameValidator.invalidJavaClassComponents("org.example.model.Artist"));
+        assertFalse(NameValidator.invalidJavaClassComponents("org.example.model"));
+    }
+
+    @Test
+    public void invalidJavaClassComponents_ReservedWords() {
+        assertTrue(NameValidator.invalidJavaClassComponents("class"));
+        assertTrue(NameValidator.invalidJavaClassComponents("com.default.Foo"));
+        assertTrue(NameValidator.invalidJavaClassComponents("org.example.switch"));
+    }
+
+    @Test
+    public void invalidJavaClassComponents_EmptyComponents() {
+        assertTrue(NameValidator.invalidJavaClassComponents(""));
+        assertTrue(NameValidator.invalidJavaClassComponents("com..Foo"));
+        assertTrue(NameValidator.invalidJavaClassComponents(".Foo"));
+        assertTrue(NameValidator.invalidJavaClassComponents("Foo."));
+        assertTrue(NameValidator.invalidJavaClassComponents(null));
+    }
+
+    @Test
+    public void invalidCharsInJavaClassName() {
+        assertNull(NameValidator.invalidCharsInJavaClassName("org.example.Artist"));
+        assertEquals("-", NameValidator.invalidCharsInJavaClassName("org.example.My-Class"));
+        assertEquals(" ", NameValidator.invalidCharsInJavaClassName("My Class"));
+    }
+
+    @Test
+    public void invalidPersistentProperty() {
+        assertTrue(NameValidator.invalidPersistentProperty("class"));
+        assertTrue(NameValidator.invalidPersistentProperty("objectId"));
+        assertTrue(NameValidator.invalidPersistentProperty("objectContext"));
+        assertTrue(NameValidator.invalidPersistentProperty("persistenceState"));
+        assertTrue(NameValidator.invalidPersistentProperty("snapshotVersion"));
+        assertTrue(NameValidator.invalidPersistentProperty(null));
+
+        assertFalse(NameValidator.invalidPersistentProperty("name"));
+        assertFalse(NameValidator.invalidPersistentProperty("classRoom"));
+    }
+}


(cayenne) 01/05: CAY-2980 Improve model name generation

Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 67a22bd1587ffb930216c9bafb32e6d3a8070242
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Mon Jul 20 15:52:21 2026 +0200

    CAY-2980 Improve model name generation
    
    clarifying special identifiers rules
---
 ai-plugin/references/model-naming-conventions.md | 7 +++++--
 ai-plugin/skills/cayenne-model-naming/SKILL.md   | 8 +++++---
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/ai-plugin/references/model-naming-conventions.md b/ai-plugin/references/model-naming-conventions.md
index 94df7ea39..32aec7471 100644
--- a/ai-plugin/references/model-naming-conventions.md
+++ b/ai-plugin/references/model-naming-conventions.md
@@ -143,8 +143,11 @@ The three cases above don't exhaust the ways a purely mechanical transliteration
 you spot a name where a human reading the underlying DB name would obviously do better, and the fix
 is defensible (not a guess), apply the same conservative treatment. Some more examples:
 
-- **Reserved words / illegal identifiers** the generator passed through — a column literally named
-  `class`, `package`, `default`, or one starting with a digit needs a legal Java name.
+- **Genuinely illegal identifiers** the generator passed through — a name starting with a digit, or
+  literally `class`, whose getter `getClass()` collides with the final `Object.getClass()`. Java
+  *keywords* are **not** a problem — `default`, `package`, `return` and the like compile fine, since
+  class generation prefixes the field/parameter name with `_` and embeds the capitalized name in the
+  accessors (`getDefault()` / `setDefault()`). Leave keyword-named properties alone.
 - **Lost acronym casing** — `HTTPURL` → `Gametype`-style collapse loses the acronym; `httpUrl` /
   `url` may read better than `httpurl`.
 - **Plural table → singular entity** — a `CUSTOMERS` table yields `Customers`; an entity is a single
diff --git a/ai-plugin/skills/cayenne-model-naming/SKILL.md b/ai-plugin/skills/cayenne-model-naming/SKILL.md
index b86aa44d7..44b1af552 100644
--- a/ai-plugin/skills/cayenne-model-naming/SKILL.md
+++ b/ai-plugin/skills/cayenne-model-naming/SKILL.md
@@ -95,9 +95,11 @@ correct — leave them.** Flag only the cases the deterministic generator can't
    names (`customer`, `orders`); a relationship is a role/property, and the prefix is noise there.
    **Leave the entity names alone** — the prefix on classes is the user's choice, and renaming
    entities regenerates classes.
-4. **Other clear, defensible improvements** — reserved words, lost acronym casing, obvious cryptic
-   abbreviations applied consistently, plural-table-to-singular-entity. Conservative by default; when
-   unsure, leave the baseline name and ask.
+4. **Other clear, defensible improvements** — illegal identifiers (digit-leading names, or `class`,
+   whose getter would clash with `Object.getClass()` — Java keywords are fine, cgen escapes them),
+   lost acronym casing, obvious cryptic abbreviations applied consistently,
+   plural-table-to-singular-entity. Conservative by default; when unsure, leave the baseline name
+   and ask.
 
 Relationship cleanup is anchored on the **DbRelationship** — it's the first-class citizen, since
 every FK has one whether or not an ObjRelationship was generated on top. The rules above (run-together,