1

Given is a class EnumTest that declares an inner enum MyEnum.

Using MyEnum as parameter type from within the class works as expected.

Using MyEnum as a parameter type outside of EnumTest fails to compile with unable to resolve class test.EnumTest.MyEnum.

I've browsed related questions, of which the best one was this, but they didn't address the specific issue of using the enum as a type.

Am I missing something very obvious here (as I'm very new to Groovy)? Or is this just another of the language's quirks "enhancements" regarding enums?

Edit: This is just a test demonstrating the issue. The actual issue happens in Jenkins JobDSL, and classpaths and imports seem to be fine there otherwise.

Groovy Version: 2.4.8
JVM: 1.8.0_201 
Vendor: Oracle Corporation
OS: Linux

$ tree test
test
├── EnumTest.groovy
├── File2.groovy
└── File3.groovy

EnumTest.groovy:

package test

public class EnumTest {
  public static enum MyEnum {
    FOO, BAR 
  }

  def doStuff(MyEnum v) {
    println v
  }
}

File2.groovy:

package test

import test.EnumTest 

// prints BAR 
new EnumTest().doStuff(EnumTest.MyEnum.BAR)

// prints FOO 
println EnumTest.MyEnum.FOO

File3.groovy:

package test

import test.EnumTest

// fails: unable to resolve class test.EnumTest.MyEnum
def thisShouldWorkIMHO(EnumTest.MyEnum v) {
   println v
}

When I'm running the test files using groovy -cp %, the output is as follows:

# groovy -cp . File2.groovy
BAR
FOO

# groovy -cp . File3.groovy
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/home/lwille/-/test/GroovyTest2.groovy: 6: unable to resolve class EnumTest.MyEnum 
 @ line 6, column 24.
   def thisShouldWorkIMHO(EnumTest.MyEnum v) {
                          ^

1 error
Leonhardt Wille
  • 557
  • 5
  • 20

1 Answers1

2

A few things worth mentioning. You don't need to import classes from the same package. Secondly, when you use a package test then you need to execute Groovy from the root folder, e.g. groovy test/File3.groovy to properly set up the classpath. (There is no need to use -cp . in such case).

Here's what it should look like.

$ tree test 
test
├── EnumTest.groovy
├── File2.groovy
└── File3.groovy

0 directories, 3 files

test/EnumTest.groovy

package test

public class EnumTest {
    public static enum MyEnum {
        FOO, BAR
    }

    def doStuff(MyEnum v) {
        println v
    }
}

test/File2.groovy

package test

// prints BAR
new EnumTest().doStuff(EnumTest.MyEnum.BAR)

// prints FOO
println EnumTest.MyEnum.FOO

test/File3.groovy

package test

// fails: unable to resolve class test.EnumTest.MyEnum
def thisShouldWorkIMHO(EnumTest.MyEnum v) {
    println v
}

thisShouldWorkIMHO(EnumTest.MyEnum.BAR)

The console output:

$ groovy test/File2.groovy 
BAR
FOO

$ groovy test/File3.groovy
BAR

However, if you want to execute script from inside the test folder then you need to specify the classpath to point to the parent folder, e.g.:

$ groovy -cp ../. File3.groovy
BAR

$ groovy -cp . File3.groovy   
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/home/wololock/workspace/groovy-sandbox/src/test/File3.groovy: 4: unable to resolve class EnumTest.MyEnum 
 @ line 4, column 24.
   def thisShouldWorkIMHO(EnumTest.MyEnum v) {
                          ^

1 error

UPDATE: the difference between Groovy 2.4 and 2.5 versions

One thing worth mentioning - the above solution works for Groovy 2.5.x and above. It is important to understand that things like methods parameters type check happen at the compiler's Phase.SEMANTIC_ANALYSIS phase. In Groovy 2.4 version, semantic analysis class resolving happens without loading classes. In case of using an inner class, it is critical to load its outer class so it can get resolved. Groovy 2.5 fixed that problem (intentionally or not) and semantic analysis resolves inner classes without an issue mentioned in this question.

For more detailed analysis, please check the following Stack Overflow question GroovyScriptEngine throws MultipleCompilationErrorsException while loading class that uses other class' static inner class where I have investigated a similar issue found in a Groovy 2.4 script. I explained there step by step how to dig down to the roots of this problem.

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
  • Thank you for the answer! If my environment was the cause, why is file 2 working? This is just a test demonstrating the issue. The actual issue happens in Jenkins JobDSL, and classpaths and imports seem to be fine there otherwise. – Leonhardt Wille Mar 29 '19 at 11:32
  • Now that I tested the suggested changes, it doesn't change anything whether or not I set the classpath or skip importing the class. My Groovy version is 2.4.8, what is yours? – Leonhardt Wille Mar 29 '19 at 12:05
  • 1
    I used Groovy 2.5.6, and I can confirm that this problem exists in any Groovy 2.4.x version (I checked 2.4.8 as well as the latest 2.4.16). Your problem sounds to be quite similar to the one I was investigating almost a year ago (https://stackoverflow.com/q/52294765/2194470). Long story short - methods parameter types are resolved at compiler's `Phase.SEMANTIC_ANALYSIS` stage. The problem here is (at least in Groovy 2.4) this compilation phase doesn't load classes, and any inner class requires its outer class to be loaded to be resolved. Groovy 2.5 solved it (intentionally or not). – Szymon Stepniak Mar 29 '19 at 12:34
  • please write that as answer and I'll accept it. Thank you for your support! – Leonhardt Wille Mar 29 '19 at 12:45
  • Done. Thanks a lot! – Szymon Stepniak Mar 29 '19 at 13:15