Loading...

December 17, 2013

Groovy Goodness: Using Package Scoped Methods, Fields and Classes

Groovy uses the Java package protected scope visibility rules to turn methods and classes into public scoped variants and fields into properties. In Java package scope visibility is applied to any field, method or class that has no explicit scope set, like public, protected or private. To get the same scope in Groovy we must use the AST transformation @PackageScope to explicitly define a field, method or class to have package scope.

In the following code we want to define a simple User class with an accessor method to change the username property. Only classes in the same package as the User class must be allowed to use this method. The method may not be invoked by classes in other packages or subclasses.

package com.mrhaki.groovy.domain

import groovy.transform.*

class User {
    private String username
    
    User(final String username) {
        this.username = username
    }

    @PackageScope 
    void changeUsername(final String newUsername) {
        this.username = newUsername
    }

    String getUsername() {
        username
    }
}

If we create a new class in another package and we want to invoke the method changeUsername() we get an error if we use @CompileStatic. We can also write a Java class to use our Groovy User class and then the compiler will also give an error.

// File: App.groovy
package com.mrhaki.groovy.app

import groovy.transform.*
import com.mrhaki.groovy.domain.User

@CompileStatic
def changeUsername() {
    final User user = new User('mrhaki')
    user.changeUsername 'Hubert A. Klein Ikkink'
}

changeUsername()

If we run the script with Groovy we get an IllegalAccessError:

$ groovyc User.groovy
$ groovy App.groovy
Caught: java.lang.IllegalAccessError: tried to access method com.mrhaki.groovy.domain.User.changeUsername(Ljava/lang/String;)V from class com.mrhaki.groovy.app.App
java.lang.IllegalAccessError: tried to access method com.mrhaki.groovy.domain.User.changeUsername(Ljava/lang/String;)V from class com.mrhaki.groovy.app.App
 at com.mrhaki.groovy.app.App.changeUser(App.groovy:10)
 at com.mrhaki.groovy.app.App.run(App.groovy:13)

If we write a Java class to invoke the package scoped method we get an error when we compile the class:

package com.mrhaki.groovy.app;

import com.mrhaki.groovy.domain.User;

public class Application {
    public static void main(final String[] args) {
        final User user = new User("mrhaki");
        user.changeUsername("Hubert A. Klein Ikkink");
    }
}

The compilation error:

$ javac -cp . Application.java
Application.java:8: error: changeUsername(String) is not public in User; cannot be accessed from outside package
        user.changeUsername("Hubert A. Klein Ikkink");
            ^
1 error

The @PackageScope annotation can be used at class, field and method level. We can also add an argument to the annotation if applied at class level. We can then use the constants in groovy.transform.PackageScopeTarget to make all fields package scoped with FIELDS, all methods with METHODS and the class with CLASS.

Code written with Groovy 2.2.1.