jdaugherty commented on code in PR #15289:
URL: https://github.com/apache/grails-core/pull/15289#discussion_r2715125824


##########
grails-bootstrap/src/main/groovy/grails/codegen/model/DomainFieldModifier.groovy:
##########
@@ -0,0 +1,347 @@
+/*
+ *  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 grails.codegen.model
+
+import groovy.transform.CompileStatic
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.FieldNode
+import org.codehaus.groovy.ast.ModuleNode
+import org.codehaus.groovy.ast.PropertyNode
+import org.codehaus.groovy.control.CompilationUnit
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.codehaus.groovy.control.Phases
+
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+
+/**
+ * Utility class for modifying domain class source files to add fields.
+ *
+ * @since 7.0
+ */
+@CompileStatic
+class DomainFieldModifier {
+
+    /**
+     * Finds the domain class file for the given class name.
+     *
+     * @param projectDir the project root directory
+     * @param className the simple class name or fully qualified class name
+     * @return the domain class file, or null if not found
+     */
+    File findDomainFile(File projectDir, String className) {
+        File domainDir = new File(projectDir, 'grails-app/domain')
+        if (!domainDir.exists()) {
+            return null
+        }
+
+        String fileName = className.replace('.', '/') + '.groovy'
+        File exactMatch = new File(domainDir, fileName)
+        if (exactMatch.exists()) {
+            return exactMatch
+        }
+
+        String simpleClassName = className.contains('.') ? 
className.substring(className.lastIndexOf('.') + 1) : className
+        File found = null
+
+        domainDir.eachFileRecurse { File file ->
+            if (file.name == "${simpleClassName}.groovy") {
+                found = file
+            }
+        }
+
+        found
+    }
+
+    /**
+     * Checks if a field with the given name already exists in the domain 
class.
+     *
+     * @param domainFile the domain class file
+     * @param fieldName the field name to check
+     * @return true if the field exists, false otherwise
+     */
+    boolean fieldExists(File domainFile, String fieldName) {
+        if (!domainFile?.exists()) {
+            return false
+        }
+
+        try {
+            ClassNode classNode = parseClass(domainFile)
+            if (classNode == null) {
+                return false
+            }
+
+            for (PropertyNode prop : classNode.properties) {
+                if (prop.name == fieldName) {
+                    return true
+                }
+            }
+
+            for (FieldNode field : classNode.fields) {
+                if (field.name == fieldName && !field.name.startsWith('$') && 
!field.name.startsWith('__')) {
+                    return true
+                }
+            }
+
+            return false
+        } catch (Exception e) {
+            return domainFile.text.contains("${fieldName}")
+        }
+    }
+
+    /**
+     * Adds a field to the domain class file.
+     *
+     * @param domainFile the domain class file
+     * @param field the field definition to add
+     * @throws IllegalStateException if the file cannot be modified
+     */
+    void addField(File domainFile, FieldDefinition field) {
+        if (!domainFile?.exists()) {
+            throw new IllegalStateException("Domain file does not exist: 
${domainFile}")
+        }
+
+        field.validate()
+
+        List<String> lines = Files.readAllLines(domainFile.toPath(), 
StandardCharsets.UTF_8)
+        InsertionPoints points = findInsertionPoints(lines)
+
+        int linesAdded = 0
+
+        if (field.usesJakartaAnnotations()) {
+            Set<String> requiredImports = field.getRequiredImports()
+            Set<String> existingImports = findExistingImports(lines)
+
+            int importsAddedCount = 0
+            for (String importClass : requiredImports) {
+                if (!existingImports.contains(importClass)) {
+                    String importLine = 'import ' + importClass
+                    lines.add(points.importInsertLine + linesAdded, importLine)
+                    linesAdded++
+                    importsAddedCount++
+                }
+            }
+
+            if (importsAddedCount > 0) {
+                int lineAfterImports = points.importInsertLine + linesAdded
+                if (lineAfterImports < lines.size()) {
+                    String nextLine = lines.get(lineAfterImports).trim()
+                    if (!nextLine.isEmpty() && !nextLine.startsWith('import 
')) {
+                        lines.add(lineAfterImports, '')
+                        linesAdded++
+                    }
+                }
+            }
+        }
+
+        int fieldInsertIndex = points.fieldInsertLine + linesAdded
+
+        if (field.usesJakartaAnnotations()) {
+            List<String> annotations = field.toAnnotations()
+            for (String annotation : annotations) {
+                lines.add(fieldInsertIndex, '    ' + annotation)
+                fieldInsertIndex++
+                linesAdded++
+            }
+        }
+
+        String fieldDeclaration = "    ${field.toFieldDeclaration()}"
+        lines.add(fieldInsertIndex, fieldDeclaration)
+        linesAdded++
+
+        if (field.usesGrailsConstraints()) {
+            String constraintLine = field.toConstraintLine()
+            if (constraintLine) {
+                if (points.hasConstraintsBlock) {
+                    int constraintInsertIndex = points.constraintInsertLine + 
linesAdded
+                    lines.add(constraintInsertIndex, '        ' + 
constraintLine)
+                } else {
+                    int constraintBlockIndex = fieldInsertIndex + 1
+                    lines.add(constraintBlockIndex, '')
+                    lines.add(constraintBlockIndex + 1, '    static 
constraints = {')

Review Comment:
   I was trying to suggest extracting the spacing instead of hard coding the 
spaces into the argument.  
   
   i.e. 
   
           int indent = 5
           "${indent*1}static constraints = {" 
   
   This way you're not hard coding various length spaces and later if we have 
to change the indent / wrap the code, we don't have to manually modify every 
line.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to