Add dynamic finders to Java Domain Classes

Hi !

I have a project with Java Domain Classes.
I would like IntelliJ to autocomplete the methods and properties with usual Grails Domain Classes (such as save(), list() and the dynamic finders)

I started to write a GroovyDsl Script, but I can't find a way to fetch all the classes of my domain package.

Moreover, in the contributor, I have to add all the methods and properties, which is impossible considering all the possibilities of the dynamics finders...

Is there a way to add all the methods of a normal Grails Domain Class at once ?

Thanks

2 comments
Comment actions Permalink

I made some tests, but I couldn't find any suitable solution.

/**
* Recursive method used to find all classes in a given directory and subdirs.
*
* @param directory The base directory
* @param packageName The package name for classes found inside the base directory
* @return The classes
* @throws ClassNotFoundException
*/
private List<Class> findClasses(File directory, final String packageName) throws ClassNotFoundException {
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists()) {
        return classes;
    }
    File[] files = directory.listFiles();
    for (File file: files) {
        if (file.isDirectory()) {
            assert !file.getName().contains(".");
            classes.addAll(findClasses(file, packageName + "." + file.getName()));
        } else if (file.getName().endsWith(".java")) {
            String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 5)
            if (!className.endsWith("Constraints") && !className.contains('$')) {
                try {
                    Class classz = Class.forName(className)
                    if(!classes.contains(classz)){
                        classes.add(classz);
                    }
                } catch (ClassNotFoundException e) {
                }
            }
        }
    }
    return classes;
}


private List<Class> findClassesInPackage(final String moduleName, final String packageName){
    final String baseFileName = <my_base_directory>
    final String srcDirName = "src/main/java/"
    final File baseAppDirectory = new File(baseFileName)


    File moduleDir = new File(baseAppDirectory, moduleName)


    String path = srcDirName + packageName.replace('.', '/');
    return findClasses(new File(moduleDir, path), packageName)
}


private List<Class> findDomainClasses(){
    return findClassesInPackage("model", "com.my.company.model")
}




//adding the Grails Domain Classes methods and properties :

final List<Class> classes = findDomainClasses()
List contextList = []
for(String className : classes){
    contextList.add(context(ctype: className))
}


contributor(contextList, {
   method name: "validate", type: Boolean
   property name: "errors", type: "org.springframework.validation.Errors"
   //etc...
})



The recursive functions to retrieve all my Domain Classes returns all the domain Classes I expected (I tried on a separate script (a .groovy one), since I can't find a way to print logs in .gdsl script)

But no class has the added methods and properties.


And when I tried with
classes = ['com.my.company.model.User', 'com.my.company.model.Account']
those two classes have the added methods and properties.
I guess the findDomainClasses() function doesn't work in .gdsl scripts, but I can't understand why, nor how to work-around this issue.

And I still have to add each method one by one, it's not very practical.

Has anybody an idea ?
0
Comment actions Permalink

Hi again :)

I finally found a way to do what I wanted.

1st of all, in order to print logs, I just use println and run Idea via command line.

For the findDomainClasses problem, I realized that the script is not launched with an application context. So the ClassLoader does not contain any of my classes.

Moreover this solution has another problem: this script is specific to my computer. If I want to work on another one, I have to change the script and set the correct "baseFileName".

Finally, the following script works.
I scan all the classes, by using an empty context, and I checked if the current class contains ".model." in its name, because all my domain classes are in the "com.my.company.model" package.
As I said in the comments, if I remove the first contributor, the second one will not work. I can't figure out why.
And I still had to write all the methods (I didn't write the ones I don't use, but it's not very complicated, once you understand the principle). But I now have the completion on methods such as "createCriteria". And even in Criteria closures, since IntelliJ now recognizes them, and adds its own autocompletion.

/**
* Add Grails Domain methods to Java Domain classes
*/


contributor(context(ctype: 'useless'), {
    //if this contributor is removed, the following will not work !!!
})


contributor(context(), {
    if (classType?.qualifiedName?.contains('.model.')) {


        method name: 'attach', type: classType?.qualifiedName, doc: 'Attaches a detached domain instance to the Hibernate session bound to the current thread'


        method name: 'clearErrors', type: 'void', doc: 'Clear the list of errors on a domain class. This may be useful if a domain class contains errors because of binding problems or validation problems. The errors may be corrected programatically. Validation may continue to fail unless the errors are cleared.'


//    property name:'constraints', type: 'java.util.Map<java.lang.String, org.codehaus.groovy.grails.validation.ConstrainedProperty>'


//    method name: 'count', type: Integer, isStatic: true


        method name: 'createCriteria', type: 'grails.orm.HibernateCriteriaBuilder', isStatic: true


        String deleteDoc = 'Indicates that a persistent instance should be deleted.'
        method name: 'delete', type: 'void', doc: deleteDoc
        method name: 'delete', type: 'void', params: [args: [flush: Boolean]], doc: deleteDoc


        method name: 'discard', type: classType?.qualifiedName, doc: 'Discards any changes that have been made during an update.'


        property name: 'errors', type: 'org.springframework.validation.Errors'


        String executeQueryDoc = 'Allows the execution of HQL queries against a domain class'
        method name: 'executeQuery', type: List, params: [query: String], doc: executeQueryDoc
        method name: 'executeQuery', type: List, params: [query: String, positionalParams: Collection], doc: executeQueryDoc
        method name: 'executeQuery', type: List, params: [query: String, positionalParams: Collection, paginateParams: Map], doc: executeQueryDoc
        method name: 'executeQuery', type: List, params: [query: String, namedParams: Map], doc: executeQueryDoc
        method name: 'executeQuery', type: List, params: [query: String, namedParams: Map, paginateParams: Map], doc: executeQueryDoc


        String updateQueryDoc = 'Allows updating a database with DML-style operation'
        method name: 'executeUpdate', type: List, params: [query: String], doc: updateQueryDoc
        method name: 'executeUpdate', type: List, params: [query: String, args: Collection], doc: updateQueryDoc
        method name: 'executeUpdate', type: List, params: [query: String, argsMap: Map], doc: updateQueryDoc


        method name: 'exists', type: Boolean, params: [id: Long], doc: 'Checks whether an instance exists for the specified id and returns true if it does'


        //find


        method name: 'get', type: classType?.qualifiedName, params: [id: Long]


        method name: 'getDirtyPropertyNames', type: 'List<String>', doc: 'Retrieve the names of modified fields in a domain class instance.'


        method name: 'getPersistentValue', type: Object, params: [fieldName: String], doc: 'Retrieve the original value of a field for a domain class instance.'


        method name: 'hasErrors', type: Boolean


        method name: 'isAttached', type: Boolean


        method name: 'isDirty', type: Boolean


        String listDoc = 'Lists all of the instances of the domain class.'
        method name: 'list', type: "java.util.List<${classType?.qualifiedName}>", isStatic: true, doc: listDoc
        method name: 'list', type: "java.util.List<${classType?.qualifiedName}>", params: [max: Integer, offset: Integer, order: String, sort: String, ignoreCase: Boolean, fetch: Map], isStatic: true, doc: listDoc


        method name: 'load', type: classType?.qualifiedName, params: [id: Long]


        method name: 'read', type: classType?.qualifiedName, params: [id: Long], isStatic: true, doc: 'Retrieves an instance of the domain class for the specified id in a read-only state, if the object doesn\'t exist null is returned.'


        method name: 'refresh', type: classType?.qualifiedName, doc: 'Refreshes a domain classes state from the database'


        String saveDoc = 'Saves a domain class instance to the database cascading updates to any child instances if required.'
        method name: 'save', type: classType?.qualifiedName, doc: saveDoc
        method name: 'save', type: classType?.qualifiedName, params: [validate: Boolean], doc: saveDoc
        method name: 'save', type: classType?.qualifiedName, params: [args: [flush: Boolean, validate: Boolean, insert: Boolean, failOnError: Boolean, deepValidate: Boolean]], doc: saveDoc


        String validateDoc = 'Validates a domain class against the applied constraints'
        method name: 'validate', type: Boolean, doc: validateDoc
        method name: 'validate', type: Boolean, params: [deepValidate: Boolean], doc: validateDoc
        method name: 'validate', type: Boolean, params: [fieldsNames: 'List<String>'], doc: validateDoc


        method name: 'withCriteria', type: Object, params: [callable: Closure], isStatic: true, doc: 'Allows inline execution of criteria with a closure.'


        method name: 'withNewSession', type: Object, params: [callable: Closure], isStatic: true, doc: 'Provides a way to execute code within the context of a completely new Hibernate session which shares the same transactional (JDBC Connection) resource as the currently bound session.'


        method name: 'withSession', type: Object, params: [callable: Closure], isStatic: true, doc: 'Provides access to the underlying Hibernate Session object'


        method name: 'withTransaction', type: Object, params: [callable: Closure], isStatic: true, doc: 'Allows programmatic transactions using Spring\'s Transaction Abstraction and a block.'


    }
})

0

Please sign in to leave a comment.