Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Domain class getter for boolean not working #1646

Closed
frangarcia opened this issue Mar 30, 2022 · 10 comments · Fixed by #1637
Closed

Domain class getter for boolean not working #1646

frangarcia opened this issue Mar 30, 2022 · 10 comments · Fixed by #1637

Comments

@frangarcia
Copy link

frangarcia commented Mar 30, 2022

Expected Behavior

Is Groovy 3, there is a new way of accessing a boolean field with a getter like this

class Book {
    String name
    Boolean active
}

Book book1 = new Book(name:"Book name", active:true)
println book1.isActive()

Actual Behaviour

The expected behaviour is not actually happening in a brand new project created using Grails 5.1.5 https://github.com/frangarcia/testprojectgrails515. This project just has a domain class domains.Book

package domains

class Book {
    String name
    Boolean active

    //Comment this method to check the error in Bootstrap when reading the isActive() getter
//    Boolean isActive() {
//        active
//    }
}

and I have added this in Bootstrap.groovy

package testprojectgrails515

import domains.Book

class BootStrap {

    def init = { servletContext ->
        Book book1 = new Book(name:"book name", active: false)
        println book1.isActive()
    }
    def destroy = {
    }
}

When running ./gradlew bootRun, we get this exception

Possible solutions: getActive(), setActive(java.lang.Boolean), save(), save(boolean), save(java.util.Map), isCase(java.lang.Object)
        at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:70)
        at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:50)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:130)
        at testprojectgrails515.BootStrap$_closure1.doCall(BootStrap.groovy:9)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:107)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
        at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:263)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1035)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1149)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1035)
        at groovy.lang.Closure.call(Closure.java:412)
        at groovy.lang.Closure.call(Closure.java:406)
        at grails.util.Environment.evaluateEnvironmentSpecificBlock(Environment.java:594)
        at grails.util.Environment.executeForEnvironment(Environment.java:587)
        at grails.util.Environment.executeForCurrentEnvironment(Environment.java:563)
        at org.grails.web.servlet.boostrap.DefaultGrailsBootstrapClass.callInit(DefaultGrailsBootstrapClass.java:74)
        at org.grails.web.servlet.context.GrailsConfigUtils.executeGrailsBootstraps(GrailsConfigUtils.java:83)
        at org.grails.plugins.web.servlet.context.BootStrapClassRunner.onStartup(BootStrapClassRunner.groovy:56)
        at grails.boot.config.GrailsApplicationPostProcessor.onApplicationEvent(GrailsApplicationPostProcessor.groovy:269)
        at grails.boot.config.GrailsApplicationPostProcessor.onApplicationEvent(GrailsApplicationPostProcessor.groovy)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:421)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:378)
        at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:938)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
        at grails.boot.GrailsApp.run(GrailsApp.groovy:99)
        at grails.boot.GrailsApp.run(GrailsApp.groovy:485)
        at grails.boot.GrailsApp.run(GrailsApp.groovy:472)
        at testprojectgrails515.Application.main(Application.groovy:11)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)

and, if we add the method isActive() to the Book domain class, we get this error

startup failed:
/Users/franciscojosegarciarico/Documents/testprojectgrails515/grails-app/domain/domains/Book.groovy: 8: Repetitive method name/signature for method 'java.lang.Boolean isActive()' in class 'domains.Book'.
 @ line 8, column 5.
       Boolean isActive() {
       ^

/Users/franciscojosegarciarico/Documents/testprojectgrails515/grails-app/domain/domains/Book.groovy: -1: Repetitive method name/signature for method 'java.lang.Boolean isActive()' in class 'domains.Book'.
 @ line -1, column -1.
2 errors

Steps To Reproduce

Just run ./gradlew bootRun and you will see one issue. Uncomment the method isActive() in the domain class Book and you will see the other issue.

Environment Information

  • Operating System: Mac OS X Version 10.15.7
  • Grails version: 5.1.5
  • JDX version: 1.8.0_322

Example Application

https://github.com/frangarcia/testprojectgrails515

Version

5.1.5

@osscontributor
Copy link
Member

If you decompile Book.class you will probably see that there is no isActive() method in the generated bytecode. If you replace book1.isActive() at grails-app/init/testprojectgrails515/BootStrap.groovy#L9 with book1.getActive() or book1.active, I would expect that code to work.

Does it?

@frangarcia
Copy link
Author

Thanks @jeffbrown for the quick feedback. Yes, your workaround works but the main concern for me is that another version of Groovy different than 3.0.7 is somehow being used preventing the isActive() method to be used. I decompiled the class Book.groovy and I can't see the method isActive().

I have tried this code locally using groovy 3.0.7 and it works fine:

class User {
    Long id
    Boolean active
    
    //This can't be defined in groovy 3 as it throws this exception 
    //Repetitive method name/signature for method 'java.lang.Boolean isActive()' in class 'User'
    /*Boolean isActive() {
        active
    }*/
}

User user1 = new User(id:1, active:false)
User user2 = new User(id:2, active:true)

assert user1.isActive()==false
assert user1.getActive()==false
assert user1.active==false

assert user2.isActive()==true
assert user2.getActive()==true
assert user2.active==true

but if I run the same code using groovy 2.5.14, the same code fails with this exception

Exception thrown

groovy.lang.MissingMethodException: No signature of method: User.isActive() is applicable for argument types: () values: []
Possible solutions: getActive(), setActive(java.lang.Boolean), isCase(java.lang.Object)
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:70)
	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:49)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:119)
	at ConsoleScript2.run(ConsoleScript2:15)
	at groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:266)
	at groovy.lang.GroovyShell.run(GroovyShell.java:376)
	at groovy.lang.GroovyShell.run(GroovyShell.java:355)
	at groovy.lang.GroovyShell.run(GroovyShell.java:174)
	at groovy.lang.GroovyShell$run$1.call(Unknown Source)
	at groovy.ui.Console$_runScriptImpl_closure22.doCall(Console.groovy:1225)
	at groovy.ui.Console$_runScriptImpl_closure22.doCall(Console.groovy)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:101)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:263)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
	at groovy.lang.Closure.call(Closure.java:405)
	at groovy.lang.Closure.call(Closure.java:399)
	at groovy.lang.Closure.run(Closure.java:486)
	at java.lang.Thread.run(Thread.java:750)

but if I uncomment the method isActive(), the code works fine. That is why I am concerned about the right version of Groovy not being picked up somehow.

@osscontributor
Copy link
Member

That is why I am concerned about the right version of Groovy not being picked up somehow.

I see. I didn't realize that was the concern. Which version is it that you believe is being picked up?

@frangarcia
Copy link
Author

Not sure about it, but according to the dependencies graph for the example project I guess it could be either groovy:2.5.6 (from org.grails.plugins:async:4.0.0) or groovy:2.4.19 (from com.bertramlabs.plugins:asset-pipeline-core:3.3.4).

@frangarcia
Copy link
Author

frangarcia commented Mar 31, 2022

Here comes the fun. If we change the field active from Boolean to boolean, the method isActive() gets properly generated.

class User {
    Long id
    Boolean active
    boolean live
    
    //This can't be defined in groovy 3 as it throws this exception 
    //Repetitive method name/signature for method 'java.lang.Boolean isActive()' in class 'User'
    /*Boolean isActive() {
        active
    }*/
}

User user1 = new User(id:1, active:false, live: false)
User user2 = new User(id:2, active:true, live: true)

assert user1.isActive()==false
assert user1.getActive()==false
assert user1.active==false

assert user2.isActive()==true
assert user2.getActive()==true
assert user2.active==true

assert user2.isLive()==true
assert user2.getLive()==true
assert user2.live==true

assert user2.isLive()==true
assert user2.getLive()==true
assert user2.live==true

println GroovySystem.getVersion()

This script, when running in Groovy 3 works fine, but when running it in Groovy 2 I get this exception:

groovy.lang.MissingMethodException: No signature of method: User.isActive() is applicable for argument types: () values: []
Possible solutions: getActive(), isLive(), setActive(java.lang.Boolean), setLive(boolean), isCase(java.lang.Object), getLive()
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:70)
	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:49)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:119)
	at booleanissuewithgetter.run(booleanissuewithgetter.groovy:16)
	at groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:266)
	at groovy.lang.GroovyShell.run(GroovyShell.java:376)
	at groovy.lang.GroovyShell.run(GroovyShell.java:355)
	at groovy.lang.GroovyShell.run(GroovyShell.java:174)
	at groovy.lang.GroovyShell$run$1.call(Unknown Source)
	at groovy.ui.Console$_runScriptImpl_closure22.doCall(Console.groovy:1225)
	at groovy.ui.Console$_runScriptImpl_closure22.doCall(Console.groovy)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:101)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:263)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
	at groovy.lang.Closure.call(Closure.java:405)
	at groovy.lang.Closure.call(Closure.java:399)
	at groovy.lang.Closure.run(Closure.java:486)
	at java.lang.Thread.run(Thread.java:750)

This makes me think even more than for any reason, the Groovy being picked up to compile the grails project is Groovy 2.

Adding some references here in case they help:

@frangarcia
Copy link
Author

frangarcia commented Mar 31, 2022

Something else we have noticed is that when the class is outside the domain class, the is getter gets properly recognised no matter whether Boolean or boolean has been used. It looks like the one in charge of processing domain classes is somehow not picking up the right version of Groovy.

@osscontributor
Copy link
Member

It looks like the one in charge of processing domain classes is somehow not picking up the right version of Groovy.

That is good info. I cannot think of any way that we could be picking up a separate version of Groovy for domain classes than the rest of the system but this is good info and will be helpful. I expect the issue has more to do with domain class transformations than it relates to somehow using the wrong version of groovy for some parts of the compilation.

Thank you for all of the feedback!

@fjloma
Copy link
Contributor

fjloma commented Mar 31, 2022

Hi,

I think the problem is with grails-data-mapping dirty check transformer. When there is no getter / setter, it will create one, so it can keep track of dirty properties.

I've been reviewing the org.grails.compiler.gorm.DirtyCheckingTransformer class from that repo. On L209 we have

    boolean booleanProperty = ClassHelper.boolean_TYPE.getName().equals(returnType.getName())

By that, booleanProperty only is true when the return type is the primitive type boolean, not Boolean class. I think this is why it works with boolean and not with Boolean.

I tried to change this booleanProperty so that it will be true for boolean and Boolean:

boolean booleanProperty = ClassHelper.boolean_TYPE.getName().equals(returnType.getName()) || ClassHelper.Boolean_TYPE.getName().equals(returnType.getName())

I've tested that this change works with a modified GormDirtyCheckingSpec :

package org.grails.datastore.gorm

import grails.gorm.annotation.Entity
import grails.gorm.tests.GormDatastoreSpec

class GormDirtyCheckingSpec extends GormDatastoreSpec {

    @Override
    List getDomainClasses() {
        [Student]
    }

    void "test a new instance is dirty by default"() {

        when:
        Student student = new Student(name: "JD", test: false)


        then:
        !student.isTest()
        student.isDirty()
    }

}

@Entity
class Student {
    String name
    Boolean test
}

@osscontributor
Copy link
Member

I think the problem is with grails-data-mapping dirty check transformer.

@fjloma That makes more sense to me than the compiler picking up the wrong version of Groovy for domain classes.

Well done!

@fjloma
Copy link
Contributor

fjloma commented Apr 1, 2022

I've made a PR for that #1637

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants