blob: 6264be7e989801757c1753e128500a513bb4647f [file] [log] [blame]
import groovy.test.GroovyTestCase
/*
* 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
*
* http://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.
*/
class ClassTest extends GroovyTestCase {
void testClassDefinition() {
assertScript '''
// tag::class_definition[]
class Person { //<1>
String name //<2>
Integer age
def increaseAge(Integer years) { //<3>
this.age += years
}
}
// end::class_definition[]
// tag::class_instantiation[]
def p = new Person()
// end::class_instantiation[]
'''
}
void testInnerClass() {
assertScript '''
// tag::inner_class[]
class Outer {
private String privateStr
def callInnerMethod() {
new Inner().methodA() //<1>
}
class Inner { //<2>
def methodA() {
println "${privateStr}." //<3>
}
}
}
// end::inner_class[]
def o = new Outer()
'''
}
void testInnerClass2() {
assertScript '''
// tag::inner_class2[]
class Outer2 {
private String privateStr = 'some string'
def startThread() {
new Thread(new Inner2()).start()
}
class Inner2 implements Runnable {
void run() {
println "${privateStr}."
}
}
}
// end::inner_class2[]
def o2 = new Outer2()
'''
}
void testAnonymousInnerClass() {
assertScript '''
// tag::anonymous_inner_class[]
class Outer3 {
private String privateStr = 'some string'
def startThread() {
new Thread(new Runnable() { //<1>
void run() {
println "${privateStr}."
}
}).start() //<2>
}
}
// end::anonymous_inner_class[]
def o3 = new Outer3()
'''
}
void testAbstractClass() {
assertScript '''
// tag::abstract_class[]
abstract class Abstract { //<1>
String name
abstract def abstractMethod() //<2>
def concreteMethod() {
println 'concrete'
}
}
// end::abstract_class[]
def ac = new Abstract() { def abstractMethod() {} }
'''
}
void testConstructorPositionalParameters() {
assertScript '''
// tag::constructor_positional_parameters[]
class PersonConstructor {
String name
Integer age
PersonConstructor(name, age) { //<1>
this.name = name
this.age = age
}
}
def person1 = new PersonConstructor('Marie', 1) //<2>
def person2 = ['Marie', 2] as PersonConstructor //<3>
PersonConstructor person3 = ['Marie', 3] //<4>
// end::constructor_positional_parameters[]
assert person1.name == 'Marie'
assert person2.name == 'Marie'
assert person3.name == 'Marie'
'''
}
void testConstructorNamedParameters() {
assertScript '''
// tag::constructor_named_parameters[]
class PersonWOConstructor { //<1>
String name
Integer age
}
def person4 = new PersonWOConstructor() //<2>
def person5 = new PersonWOConstructor(name: 'Marie') //<3>
def person6 = new PersonWOConstructor(age: 1) //<4>
def person7 = new PersonWOConstructor(name: 'Marie', age: 2) //<5>
// end::constructor_named_parameters[]
assert person4.name == null
'''
}
void testInterfaceDefinition() {
assertScript '''
// tag::interface_def_1[]
interface Greeter { // <1>
void greet(String name) // <2>
}
// end::interface_def_1[]
// tag::class_implements[]
class SystemGreeter implements Greeter { // <1>
void greet(String name) { // <2>
println "Hello $name"
}
}
def greeter = new SystemGreeter()
assert greeter instanceof Greeter // <3>
// end::class_implements[]
greeter.greet('Laura')
// tag::extended_interface[]
interface ExtendedGreeter extends Greeter { // <1>
void sayBye(String name)
}
// end::extended_interface[]
// tag::no_structural_interface[]
class DefaultGreeter {
void greet(String name) { println "Hello" }
}
greeter = new DefaultGreeter()
assert !(greeter instanceof Greeter)
// end::no_structural_interface[]
def coerced
// tag::interface_coercion[]
greeter = new DefaultGreeter() // <1>
coerced = greeter as Greeter // <2>
assert coerced instanceof Greeter // <3>
// end::interface_coercion[]
'''
def err = shouldFail {
assertScript '''
// tag::protected_forbidden[]
interface Greeter {
protected void greet(String name) // <1>
}
// end::protected_forbidden[]
1
'''
}
assert err.contains("Method 'greet' is protected but should be public in interface 'Greeter'")
err = shouldFail {
assertScript '''
// tag::private_forbidden[]
interface Greeter {
private void greet(String name)
}
// end::private_forbidden[]
1
'''
}
assert err.contains("Method 'greet' is private but should be public in interface 'Greeter'")
}
void testFields() {
assertScript '''
// tag::field_declaration[]
class Data {
private int id // <1>
protected String description // <2>
public static final boolean DEBUG = false // <3>
}
// end::field_declaration[]
def d = new Data()
'''
assertScript '''
class IDGenerator { static int next() {0} }
// tag::field_initialization[]
class Data {
private String id = IDGenerator.next() // <1>
// ...
}
// end::field_initialization[]
new Data()
'''
assertScript '''
// tag::typing_fields[]
class BadPractice {
private mapping // <1>
}
class GoodPractice {
private Map<String,String> mapping // <2>
}
// end::typing_fields[]
BadPractice
'''
}
void testProperties() {
assertScript '''
// tag::properties_definition[]
class Person {
String name // <1>
int age // <2>
}
// end::properties_definition[]
assert Person.declaredFields.name.containsAll (['name','age'])
assert Person.getDeclaredMethod('getName')
assert Person.getDeclaredMethod('getAge')
assert Person.getDeclaredMethod('setName',String)
assert Person.getDeclaredMethod('setAge',int)
'''
assertScript '''
// tag::readonly_property[]
class Person {
final String name // <1>
final int age // <2>
Person(String name, int age) {
this.name = name // <3>
this.age = age // <4>
}
}
// end::readonly_property[]
def p = new Person('Bob', 42)
assert Person.declaredFields.name.containsAll (['name','age'])
assert Person.getDeclaredMethod('getName')
assert Person.getDeclaredMethod('getAge')
try {
assert Person.getDeclaredMethod('setName',String) == null
} catch (NoSuchMethodException e) {
}
try {
assert Person.getDeclaredMethod('setAge',int) == null
} catch (NoSuchMethodException e) {
}
'''
assertScript '''
// tag::property_access[]
class Person {
String name
void name(String name) {
this.name = "Wonder$name" // <1>
}
String wonder() {
this.name // <2>
}
}
def p = new Person()
p.name = 'Marge' // <3>
assert p.name == 'Marge' // <4>
p.name('Marge') // <5>
assert p.wonder() == 'WonderMarge' // <6>
// end::property_access[]
'''
assertScript '''
// tag::properties_meta[]
class Person {
String name
int age
}
def p = new Person()
assert p.properties.keySet().containsAll(['name','age'])
// end::properties_meta[]
'''
assertScript '''
// tag::pseudo_properties[]
class PseudoProperties {
// a pseudo property "name"
void setName(String name) {}
String getName() {}
// a pseudo read-only property "age"
int getAge() { 42 }
// a pseudo write-only property "groovy"
void setGroovy(boolean groovy) { }
}
def p = new PseudoProperties()
p.name = 'Foo' // <1>
assert p.age == 42 // <2>
p.groovy = true // <3>
// end::pseudo_properties[]
'''
}
void testDefineAnnotation() {
assertScript '''
// tag::define_annotation[]
@interface SomeAnnotation {}
// end::define_annotation[]
SomeAnnotation
'''
}
void testAnnotationMembers() {
assertScript '''
// tag::ann_member_string[]
@interface SomeAnnotation {
String value() // <1>
}
// end::ann_member_string[]
SomeAnnotation
'''
assertScript '''
// tag::ann_member_string_default[]
@interface SomeAnnotation {
String value() default 'something' // <2>
}
// end::ann_member_string_default[]
SomeAnnotation
'''
assertScript '''
// tag::ann_member_int[]
@interface SomeAnnotation {
int step() // <3>
}
// end::ann_member_int[]
SomeAnnotation
'''
assertScript '''
// tag::ann_member_class[]
@interface SomeAnnotation {
Class appliesTo() // <4>
}
// end::ann_member_class[]
SomeAnnotation
'''
assertScript '''
// tag::ann_member_annotation[]
@interface SomeAnnotation {}
@interface SomeAnnotations {
SomeAnnotation[] value() // <5>
}
// end::ann_member_annotation[]
SomeAnnotation
'''
assertScript '''
// tag::ann_member_enum[]
enum DayOfWeek { mon, tue, wed, thu, fri, sat, sun }
@interface Scheduled {
DayOfWeek dayOfWeek() // <6>
}
// end::ann_member_enum[]
Scheduled
'''
}
void testApplyAnnotation() {
assertScript '''
@interface SomeAnnotation {
int value() default 0
}
// tag::apply_annotation_1[]
@SomeAnnotation // <1>
void someMethod() {
// ...
}
@SomeAnnotation // <2>
class SomeClass {}
@SomeAnnotation String var // <3>
// end::apply_annotation_1[]
someMethod()
'''
assertScript '''
// tag::annotation_value_set[]
@interface Page {
int statusCode()
}
@Page(statusCode=404)
void notFound() {
// ...
}
// end::annotation_value_set[]
'''
assertScript '''
// tag::annotation_value_set_option[]
@interface Page {
String value()
int statusCode() default 200
}
@Page(value='/home') // <1>
void home() {
// ...
}
@Page('/users') // <2>
void userList() {
// ...
}
@Page(value='error',statusCode=404) // <3>
void notFound() {
// ...
}
// end::annotation_value_set_option[]
'''
}
void testAnnotationTarget() {
assertScript '''
// tag::ann_target[]
import java.lang.annotation.ElementType
import java.lang.annotation.Target
@Target([ElementType.METHOD, ElementType.TYPE]) // <1>
@interface SomeAnnotation {} // <2>
// end::ann_target[]
SomeAnnotation
'''
}
void testAnnotationRetention() {
assertScript '''
// tag::ann_retention[]
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
@Retention(RetentionPolicy.SOURCE) // <1>
@interface SomeAnnotation {} // <2>
// end::ann_retention[]
SomeAnnotation
'''
}
void testClosureAsAnnotationValue() {
assertScript '''
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.reflect.Modifier
// tag::closure_ann_def[]
@Retention(RetentionPolicy.RUNTIME)
@interface OnlyIf {
Class value() // <1>
}
// end::closure_ann_def[]
// tag::closure_ann_example[]
class Tasks {
Set result = []
void alwaysExecuted() {
result << 1
}
@OnlyIf({ jdk>=6 })
void supportedOnlyInJDK6() {
result << 'JDK 6'
}
@OnlyIf({ jdk>=7 && windows })
void requiresJDK7AndWindows() {
result << 'JDK 7 Windows'
}
}
// end::closure_ann_example[]
// tag::closure_ann_runner[]
class Runner {
static <T> T run(Class<T> taskClass) {
def tasks = taskClass.newInstance() // <1>
def params = [jdk:6, windows: false] // <2>
tasks.class.declaredMethods.each { m -> // <3>
if (Modifier.isPublic(m.modifiers) && m.parameterTypes.length == 0) { // <4>
def onlyIf = m.getAnnotation(OnlyIf) // <5>
if (onlyIf) {
Closure cl = onlyIf.value().newInstance(tasks,tasks) // <6>
cl.delegate = params // <7>
if (cl()) { // <8>
m.invoke(tasks) // <9>
}
} else {
m.invoke(tasks) // <10>
}
}
}
tasks // <11>
}
}
// end::closure_ann_runner[]
// tag::closure_ann_runner_exec[]
def tasks = Runner.run(Tasks)
assert tasks.result == [1, 'JDK 6'] as Set
// end::closure_ann_runner_exec[]
'''
}
void testAnnotationCollector() {
assertScript '''import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
@Retention(RetentionPolicy.RUNTIME)
@interface Service {}
@Retention(RetentionPolicy.RUNTIME)
@interface Transactional {}
// tag::transactionalservice_class[]
@Service
@Transactional
class MyTransactionalService {}
// end::transactionalservice_class[]
MyTransactionalService
'''
assertScript '''import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
@Retention(RetentionPolicy.RUNTIME)
@interface Service {}
@Retention(RetentionPolicy.RUNTIME)
@interface Transactional {}
// tag::metaann_ts[]
import groovy.transform.AnnotationCollector
@Service // <1>
@Transactional // <2>
@AnnotationCollector // <3>
@interface TransactionalService {
}
// end::metaann_ts[]
// tag::transactionalservice_class2[]
@TransactionalService // <1>
class MyTransactionalService {}
// end::transactionalservice_class2[]
// tag::annotations_expanded[]
def annotations = MyTransactionalService.annotations*.annotationType()
assert (Service in annotations)
assert (Transactional in annotations)
// end::annotations_expanded[]
'''
}
void testAnnotationCollectorWithParams() {
assertScript '''
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import groovy.transform.AnnotationCollector
@Retention(RetentionPolicy.RUNTIME)
@interface Timeout { int after() }
@Retention(RetentionPolicy.RUNTIME)
@interface Dangerous { String type() }
// tag::collected_ann_explosive[]
@Timeout(after=3600)
@Dangerous(type='explosive')
// end::collected_ann_explosive[]
// tag::collector_ann_explosive[]
@AnnotationCollector
public @interface Explosive {}
// end::collector_ann_explosive[]
// tag::example_bomb[]
@Explosive(after=0) // <1>
class Bomb {}
// end::example_bomb[]
@Explosive
class ReferenceBomb {}
assert Bomb.getAnnotation(Timeout).after() == 0
assert ReferenceBomb.getAnnotation(Timeout).after() == 3600
'''
}
void testAnnotationCollectorWithSameParamNames() {
assertScript '''
import groovy.transform.AnnotationCollector
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
// tag::collector_ann_same_values[]
@Retention(RetentionPolicy.RUNTIME)
public @interface Foo {
String value() // <1>
}
@Retention(RetentionPolicy.RUNTIME)
public @interface Bar {
String value() // <2>
}
@Foo
@Bar
@AnnotationCollector
public @interface FooBar {} // <3>
@Foo('a')
@Bar('b')
class Bob {} // <4>
assert Bob.getAnnotation(Foo).value() == 'a' // <5>
println Bob.getAnnotation(Bar).value() == 'b' // <6>
@FooBar('a')
class Joe {} // <7>
assert Joe.getAnnotation(Foo).value() == 'a' // <8>
println Joe.getAnnotation(Bar).value() == 'a' // <9>
// end::collector_ann_same_values[]
'''
def err = shouldFail '''
import groovy.transform.AnnotationCollector
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
@Retention(RetentionPolicy.RUNTIME)
public @interface Foo {
String value()
}
@Retention(RetentionPolicy.RUNTIME)
public @interface Bar {
int value()
}
@Foo
@Bar
@AnnotationCollector
public @interface FooBar {}
@FooBar(666)
class Evil {}
Evil
'''
assert err =~ /Attribute 'value' should have type 'java.lang.String'; but found type 'int' in @Foo/
}
void testCustomProcessor() {
assertScript '''import groovy.transform.AnnotationCollector
import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
// tag::compiledynamic_naive[]
@CompileStatic(TypeCheckingMode.SKIP)
@AnnotationCollector
public @interface CompileDynamic {}
// end::compiledynamic_naive[]
@CompileDynamic
class Foo {}
Foo
'''
assertScript '''import groovy.transform.AnnotationCollector
import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import org.codehaus.groovy.ast.AnnotatedNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.expr.ClassExpression
import org.codehaus.groovy.ast.expr.PropertyExpression
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.AnnotationCollectorTransform
// tag::compiledynamic_def_fixed[]
@AnnotationCollector(processor = "org.codehaus.groovy.transform.CompileDynamicProcessor")
public @interface CompileDynamic {
}
// end::compiledynamic_def_fixed[]
// tag::compiledynamic_processor[]
@CompileStatic // <1>
class CompileDynamicProcessor extends AnnotationCollectorTransform { // <2>
private static final ClassNode CS_NODE = ClassHelper.make(CompileStatic) // <3>
private static final ClassNode TC_NODE = ClassHelper.make(TypeCheckingMode) // <4>
List<AnnotationNode> visit(AnnotationNode collector, // <5>
AnnotationNode aliasAnnotationUsage, // <6>
AnnotatedNode aliasAnnotated, // <7>
SourceUnit source) { // <8>
def node = new AnnotationNode(CS_NODE) // <9>
def enumRef = new PropertyExpression(
new ClassExpression(TC_NODE), "SKIP") // <10>
node.addMember("value", enumRef) // <11>
Collections.singletonList(node) // <12>
}
}
// end::compiledynamic_processor[]
@CompileDynamic
class Foo {}
Foo
'''
}
}