Giter VIP home page Giter VIP logo

groovy-decorator's Introduction

Groovy Decorator

Python-inspired method decorator for Groovy.

Groovy 2.4.6 Java 1.7.0_75 Build Status [Maven Central Releases](http://search.maven.org/#search|ga|1|g:"com.github.yihtserns" AND a:"groovy-decorator")

Example

// Guard.groovy in its own project
import com.github.yihtserns.groovy.decorator.MethodDecorator
import com.github.yihtserns.groovy.decorator.Function
import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import java.lang.annotation.ElementType

@MethodDecorator({ Function func ->
    return { args ->
        String username = args[0]

        if (username == 'hacker') {
            throw new UnsupportedOperationException("hacker not allowed")
        } else {
            func(args) // Call original method
        }
    }
})
@GroovyASTTransformationClass("com.github.yihtserns.groovy.decorator.DecoratorASTTransformation")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Guard {
}
// SomeScript.groovy
class SomeOperation {

    @Guard
    public String doStuff(String username, int secretCode) {
        println "${username}: ${secretCode}"
    }
}

def op = new SomeOperation()
op.doStuff('good guy', 3) // prints 'good guy: 3'
op.doStuff('hacker', 1) // throws UnsupportedOperationException

Using annotation elements

// Guard.groovy in its own project
import com.github.yihtserns.groovy.decorator.MethodDecorator
import com.github.yihtserns.groovy.decorator.Function
import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import java.lang.annotation.ElementType

@MethodDecorator({ Function func, Guard guard ->
    String[] prohibited = guard.against()

    return { args ->
        String username = args[0]

        if (prohibited.contains(username)) {
            throw new UnsupportedOperationException("$username not allowed")
        } else {
            func(args) // Call original method
        }
    }
})
@GroovyASTTransformationClass("com.github.yihtserns.groovy.decorator.DecoratorASTTransformation")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Guard {

    String[] against() default ['hacker']
}
// SomeScript.groovy
class SomeOperation {

    @Guard
    public String doStuff(String username, int secretCode) {
        println "${username}: ${secretCode}"
    }

    @Guard(against = ['good guy', 'hacker'])
    public String doSuperSensitiveStuff(String username, int secretCode) {
        println "${username}: ${secretCode}"
    }
}

def op = new SomeOperation()

op.doStuff('admin', 10) // prints 'admin: 10'
op.doStuff('good guy', 3) // prints 'good guy: 3'
op.doStuff('hacker', 1) // throws UnsupportedOperationException

op.doSuperSensitiveStuff('admin', 10) // prints 'admin: 10'
op.doSuperSensitiveStuff('good guy', 3) // throws UnsupportedOperationException
op.doSuperSensitiveStuff('hacker', 1) // throws UnsupportedOperationException

Ad-hoc method decoration

// SomeScript.groovy
import com.github.yihtserns.groovy.decorator.Intercept
import com.github.yihtserns.groovy.decorator.Function

class SomeOperation {

    @Intercept({ Function func, args ->
        String username = args[0]

        if (username == 'hacker') {
            throw new UnsupportedOperationException("hacker not allowed")
        } else {
            func(args) // Call original method
        }
    })
    public String doStuff(String username, int secretCode) {
        println "${username}: ${secretCode}"
    }
}

def op = new SomeOperation()
op.doStuff('good guy', 3) // prints 'good guy: 3'
op.doStuff('hacker', 1) // throws UnsupportedOperationException

Sharing ad-hoc method decoration

// SomeScript.groovy
import com.github.yihtserns.groovy.decorator.Intercept
import com.github.yihtserns.groovy.decorator.Function

class SomeOperation {

    @Intercept(BlockHacker)
    public String doStuff(String username, int secretCode) {
        println "${username}: ${secretCode}"
    }

    @Intercept(BlockHacker)
    public String doAnotherStuff(String username, int secretCode) {
        println "${username}: ${secretCode}"
    }

    private static class BlockHacker extends Closure {

        BlockHacker(owner, thisObject) {
            super(owner, thisObject)
        }

        def doCall(Function func, args) {
            String username = args[0]

            if (username == 'hacker') {
                throw new UnsupportedOperationException("hacker not allowed")
            } else {
                func(args) // Call original method
            }
        }
    }
}

def op = new SomeOperation()

op.doStuff('admin', 10) // prints 'admin: 10'
op.doStuff('good guy', 3) // prints 'good guy: 3'
op.doStuff('hacker', 1) // throws UnsupportedOperationException

op.doAnotherStuff('admin', 10) // prints 'admin: 10'
op.doAnotherStuff('good guy', 3) // prints 'good guy: 3'
op.doAnotherStuff('hacker', 1) // throws UnsupportedOperationException

API

ย  Description
Function.name : String Name of the decorated method.
Function.returnType : Class<?> Return type of the decorated method.

The way it works is similar to groovy.transform.Memoized, in that it turns:

class MyClass {

    @Decorator1(el1 = val1, el2 = val2,... elN = valN)
    @Decorator2
    boolean method(String x, int y) {
        ...
    }

    @Decorator1
    String method(x.Input input1, y.Input input2) {
        ...
    }

    @Decorator1
    String method(y.Input input1, x.Input input2) {
        ...
    }
}

into:

class MyClass {

    private Function decorating$methodStringint = Function.create({ String x, int y -> decorated$method(x, y) }, boolean)
                                                          .decorateWith(
                                                              /** Decorator1 annotated on method(String, int) **/,
                                                              /** Decorator1's decorator closure **/)
                                                          .decorateWith(
                                                              /** Decorator2 annotated on method(String, int) **/,
                                                              /** Decorator2's decorator closure **/)
    private Function decorating$methodInputInput = Function.create({ x.Input input1, y.Input input2 -> decorated$method(input1, input2) }, String)
                                                           .decorateWith(
                                                               /** Decorator1 annotated on method(x.Input, y.Input) **/,
                                                               /** Decorator1's decorator closure **/)
    private Function _decorating$methodInputInput = Function.create({ y.Input input1, x.Input input2 -> decorated$method(input1, input2) }, String)
                                                            .decorateWith(
                                                                /** Decorator1 annotated on method(y.Input, x.Input) **/,
                                                                /** Decorator1's decorator closure **/)

    @Decorator1(el1 = val1, el2 = val2,... elN = valN)
    @Decorator2
    boolean method(String x, int y) {
        decorating$methodStringint([x, y])
    }

    private boolean decorated$method(String x, int y) {
        ...
    }

    @Decorator1
    String method(x.Input input1, y.Input input2) {
        decorating$methodInputInput([input1, input2])
    }

    private String decorated$method(x.Input input1, y.Input input2) {
        ...
    }

    @Decorator1
    String method(y.Input input1, x.Input input2) {
        _decorating$methodInputInput([input1, input2])
    }

    private String decorated$method(y.Input input1, x.Input input2) {
        ...
    }
}

Multiple decoration ordering

@Decorator1 // Decorate with this first, then
@Decorator2 // Decorate with this, then
@Decorator3 // Decorate with this
String getSecret(user, secretId) {
  ...
}

Example:

@Allow(Role.FIELD_AGENT)
@CacheResult
@Log(entry=true, exit=true)
String getSecret(user, secretId) {
  ...
}

...
getSecret('707', 707) // -> Log -> CacheResult -> Allow -> greet(user, secretId)

Limitations

Cannot work with @CompileStatic for Groovy version < 2.3.9

An exception will be thrown:

java.lang.ArrayIndexOutOfBoundsException: Internal compiler error while compiling script1457454321240940813275.groovy
Method: MethodNode@1527752119[java.lang.Object doCall(java.lang.Object)]
Line -1, expecting casting to java.lang.Object but operand stack is empty
	at org.codehaus.groovy.classgen.asm.OperandStack.doConvertAndCast(OperandStack.java:323)
	at org.codehaus.groovy.classgen.asm.OperandStack.doGroovyCast(OperandStack.java:290)
...

groovy-decorator's People

Contributors

ceilfors avatar yihtserns avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

groovy-decorator's Issues

Check if order of method decoration is intuitive

E.g.

Example 1

@Log(entry=true, exit=true)
@Allow(Role.FIELD_AGENT)
@CacheResult
String getSecret(user, secretId) {
  ...
}

Example 2

@CacheResult
@Log(entry=true, exit=true)
@Allow(Role.FIELD_AGENT)
String getSecret(user, secretId) {
  ...
}

Example 3

@Log(entry=true, exit=true)
@CacheResult
@Allow(Role.FIELD_AGENT)
String getSecret(user, secretId) {
  ...
}

Example 4

@Allow(Role.FIELD_AGENT)
@CacheResult
@Log(entry=true, exit=true)
String getSecret(user, secretId) {
  ...
}

Since CacheResult caches the result, looking at the annotations, how many times would a person think Allow (which check if user is authorized to call method) and Log will be called for each example?

Should return original method's return type

@Test
public void "should return original method's return type"() {
    Class clazz = cl.parseClass("class Greeter { void greet(name) { } }")

    doNothing: {
        def decorate = { func, args -> }
        def func = Function.create(clazz, 'greet', [String])
        clazz.metaClass.greet = { String name ->
            decorate(func.curry(delegate), [name])
        }
    }
    getReturnType: {
        def decorate = { func, args -> func.method.returnType }
        def func = Function.create(clazz, 'greet', [String])
        clazz.metaClass.greet = { String name ->
            decorate(func.curry(delegate), [name])
        }
    }

    def greeter = clazz.newInstance()
    assert greeter.greet('Noel') == void.class
}

Support static compilation

Currently when static compilation is enabled (e.g. via @StaticCompile), an exception will be thrown:

General error during class generation: size==0

java.lang.ArrayIndexOutOfBoundsException: size==0
    at org.codehaus.groovy.classgen.asm.OperandStack.getTopOperand(OperandStack.java:729)
    at org.codehaus.groovy.classgen.asm.BinaryExpressionHelper.evaluateEqual(BinaryExpressionHelper.java:306)
...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.