Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Often, we want to intercept more than one class in one block. This example is of an aliasing interceptor, which disables some English-language names for selected classes, and replaces them with Spanish-language names. We re-use the UninterceptedInterceptor class and useInterceptor utility from the previous examplesexample.

Code Block
import org.codehaus.groovy.runtime.InvokerHelper

abstract class AliasInterceptor extends UninterceptedInterceptor{
  protected aliases= [:]

  private toReturn= null, toThrow= false, toInvoke= false

  Object doBefore( Object object, String methodName, Object[] arguments ){
    if( methodName in aliases.keySet() )
      toReturn= InvokerHelper.invokeMethod( object, aliases[methodName],
                                            arguments )
                                                 //use Spanish names instead
    else if( methodName in aliases.values() ) toThrow= true
                                                 //disable the English names
    else toInvoke= true //run other methods unchanged
    null
  }
  Object doAfter( Object object, String methodName, Object[] arguments,
                  Object result ){
    if( toReturn != null ){
      result= toReturn
      toReturn= null
    }else if( toThrow ){
      toThrow= false
      throw new MissingMethodException( methodName, object.getClass(),
                                        arguments )
    }else toInvoke= false
    result
  }
  boolean doInvoke(){ toInvoke }
}

class ArrayListAliasInterceptor extends AliasInterceptor{
  {aliases.putAll( [tamano:'size', todos:'each' ] )} //Spanish aliases
}

class HashMapAliasInterceptor extends AliasInterceptor{
  {aliases.putAll( [tamano:'size', todos:'each' ] )}
}

class LinkedHashMapAliasInterceptor extends AliasInterceptor{
  {aliases.putAll( [tamano:'size', todos:'each' ] )}
}

...

Code Block
def useAliasing= { Closure c->
  useInterceptor(ArrayList, ArrayListAliasInterceptor){
    useInterceptor(HashMap, HashMapAliasInterceptor){
      useInterceptor(LinkedHashMap, LinkedHashMapAliasInterceptor){
//although LinkedHashMap is descended from HashMap, set up interception separately
        c()
      }
    }
  }
}

useAliasing{
  def a= [1, 3, 5, 7, 9]
  println 'size: '+ a.tamano()
                      //Spanish 'tamano' is an alias for the 'size' method
  try{ println a.size(); assert 0 }
  catch(e){ assert e instanceof MissingMethodException }
                      //English 'size' method disabled
  a.todos{ println 'item: '+ it }
  println ''

  def b= [a:1, c:3, e:5, g:7]
  println 'size: '+ b.tamano()
  try{ println b.size(); assert 0 }
  catch(e){ assert e instanceof MissingMethodException }
  b.todos{ println 'item: '+ it }
  println ''

  def c= new LinkedHashMap( [e:5, g:7, i:9] )
  println 'size: '+ c.tamano()
  try{ println c.size(); assert 0 }
  catch(e){ assert e instanceof MissingMethodException }
  c.todos{ println 'item: '+ it }
}

...

Code Block
class Extras{
  static closureInject(List self, Closure base){
    def z= []
    self.eachWithIndex{ it, i-> z<< {-> it( z[i+1] )} }
    z<< base
    z[0]()
  }
}

use(Extras){
  [ {c-> useInterceptor(ArrayList, ArrayListAliasInterceptor){ c() }},
    {c-> useInterceptor(HashMap, HashMapAliasInterceptor){ c() }},
    {c-> useInterceptor(LinkedHashMap, LinkedHashMapAliasInterceptor){ c() }},

  ].closureInject{
    def a= [1, 3, 5, 7, 9],
        b= [a:1, c:3, e:5, g:7],
        c= new LinkedHashMap( [e:5, g:7, i:9] )

    println 'size: '+ a.tamano()
    try{ println a.size(); assert 0 }
    catch(e){ assert e instanceof MissingMethodException }
    a.todos{ println 'item: '+ it }
    println ''

    println 'size: '+ b.tamano()
    try{ println b.size(); assert 0 }
    catch(e){ assert e instanceof MissingMethodException }
    b.todos{ println 'item: '+ it }
    println ''

    println 'size: '+ c.tamano()
    try{ println c.size(); assert 0 }
    catch(e){ assert e instanceof MissingMethodException }
    c.todos{ println 'item: '+ it }
  }
}

...

Code Block
import org.codehaus.groovy.runtime.InvokerHelper

public class MyProxyMetaClass extends MetaClassImpl{
    protected adaptee= null
    def interceptor= null
    MyProxyMetaClass(MetaClassRegistry registry, Class theClass,
                     MetaClass adaptee){
        super(registry, theClass); this.adaptee = adaptee
    }
    static getInstance(Class theClass){
        def metaRegistry = InvokerHelper.getInstance().getMetaRegistry()
        new MyProxyMetaClass(metaRegistry, theClass, 
                             metaRegistry.getMetaClass(theClass) )
    }
    void use(Closure closure){
        registry.setMetaClass(theClass, this)
        try{ closure.call() }
        finally{ registry.setMetaClass(theClass, adaptee) }
    }
    void use(GroovyObject object, Closure closure){
        object.setMetaClass(this)
        try{ closure.call() }
        finally{ object.setMetaClass(adaptee) }
    }
    Object invokeMethod(final Object object, final String methodName,
                        final Object[] arguments){
        doCall(object, methodName, arguments,
               { adaptee.invokeMethod(object, methodName, arguments) } )
    }
    Object invokeStaticMethod(final Object object, final String methodName,
                              final Object[] arguments){
        doCall(object, methodName, arguments,
               { adaptee.invokeStaticMethod(object, methodName, arguments) } )
    }
    Object invokeConstructor(final Object[] arguments){
        doCall(theClass, "ctor", arguments,
               { adaptee.invokeConstructor(arguments) } )
    }
    Object invokeConstructorAt(final Class at, final Object[] arguments){
        doCall(theClass, "ctor", arguments,
               { adaptee.invokeConstructorAt(at, arguments) } )
    }
    private Object doCall(Object object, String methodName, Object[] arguments,
                          Closure howToInvoke){
        if (null == interceptor){ return howToInvoke.call() }
        interceptor.aroundInvoke(object, methodName, arguments, howToInvoke)
    }
}

interface AroundInterceptor{
  Object aroundInvoke(Object object, String methodName, Object[] arguments,
                      Closure proceed)
}

We can then run our code:

Code Block
class MyInterceptor implements AroundInterceptor{
  Object aroundInvoke(Object object, String methodName, Object[] arguments,
                      Closure proceed){
    println "  BEFORE $object .$methodName $arguments"
    def result= proceed()
    println "  AFTER $object .$methodName $arguments: $result"
    result
  }
}

class MyClass{
  void sayHi(){ System.out.println 'hi' }
}

def interceptor= new MyInterceptor()
def proxy= MyProxyMetaClass.getInstance( MyClass )
proxy.use{
  proxy.interceptor= interceptor
  new MyClass().sayHi()
}

/*outputs:
  BEFORE class MyClass .ctor {}
  AFTER class MyClass .ctor {}: MyClass@1f5d386
  BEFORE MyClass@1f5d386 .sayHi {}
hi
  AFTER MyClass@1f5d386 .sayHi {}: null
*/

...

Code Block
import org.codehaus.groovy.runtime.InvokerHelper

public class MultiInterceptorProxyMetaClass extends MetaClassImpl{
  protected adaptee= null
  def interceptors= [] //reference a list of interceptors, instead of just one

  MultiInterceptorProxyMetaClass( MetaClassRegistry registry, Class theClass,
                                  MetaClass adaptee ){
    super(registry, theClass)
    this.adaptee = adaptee
    if( null == adaptee )
        throw new IllegalArgumentException( "adaptee must not be null" )
  }
  static getInstance(Class theClass){
    def metaRegistry= InvokerHelper.getInstance().getMetaRegistry()
    new MultiInterceptorProxyMetaClass( metaRegistry, theClass, 
                                        metaRegistry.getMetaClass(theClass) )
  }
  void use(Closure closure){
    registry.setMetaClass(theClass, this)
    registry.getMetaClass(theClass).initialize()
    try{ closure.call() }
    finally{ registry.setMetaClass(theClass, adaptee) }
  }
  void use(GroovyObject object, Closure closure){
    object.setMetaClass(this)
    try{ closure.call() }
    finally{ object.setMetaClass(adaptee) }
  }
  Object invokeMethod( final Object object, final String methodName,
                       final Object[] arguments ){
    doCall(object, methodName, arguments,
           { adaptee.invokeMethod(object, methodName, arguments) } )
  }
  Object invokeStaticMethod( final Object object, final String methodName,
                             final Object[] arguments ){
    doCall(object, methodName, arguments,
           { adaptee.invokeStaticMethod(object, methodName, arguments) } )
  }
  Object invokeConstructor(final Object[] arguments){
    doCall(theClass, "ctor", arguments,
           { adaptee.invokeConstructor(arguments) } )
  }
  public Object invokeConstructorAt(final Class at, final Object[] arguments){
    doCall(theClass, "ctor", arguments,
           { adaptee.invokeConstructorAt(at, arguments) } )
  }
  private Object doCall( Object object, String methodName, Object[] arguments,
                         Closure howToInvoke ){
    if( interceptors == [] ){ return howToInvoke.call() }
    def result
    interceptors.each{ //different logic to cater for all the interceptors

      result= it.beforeInvoke(object, methodName, arguments)
      if( it.doInvoke() ){ result= howToInvoke.call() }
      it.afterInvoke(object, methodName, arguments, result)
    }
    result
  }
}

...

Code Block
abstract class ObserverProtocol implements Interceptor{
  private perSubjectObservers

  protected getObservers( subject ){ 
    if( perSubjectObservers == null ) perSubjectObservers= [:]
    def observers= perSubjectObservers[ subject ]
    if( observers == null ){
      observers= []
      perSubjectObservers[ subject ]= observers
    }
    observers
  }

  public void addObserver( subject, observer ){
    getObservers(subject) << observer
  }
  public void removeObserver( subject, observer ){
    getObservers(subject).remove(observer)
  }

  abstract Object beforeInvoke( Object object, String methodName,
                                Object[] arguments )

  abstract boolean doInvoke()

  abstract Object afterInvoke( Object object, String methodName,
                               Object[] arguments, Object result )
}

...

Code Block
public class Screen{ //functional class to be observerdobserved
  def name
  public Screen( String s ){
    this.name= s
  }
  public void display( String s ){
    println(this.name + ": " + s)
  }
}

public class Point{ //class to be observed
  def x, y, color
  public Point( int x, int y, Color color ){
    this.x=x
    this.y=y
    this.color=color
  }
}

class ColorObserver extends ObserverProtocol{
  Object beforeInvoke( Object object, String methodName, Object[] arguments ){
    null
  }
  boolean doInvoke(){ true }
  Object afterInvoke( Object object, String methodName, Object[] arguments,
                      Object result ){
    if( object instanceof Point && methodName == 'setColor' ){
      getObservers(object).each{
        it.display("Screen updated (point subject changed color).")
      }
    }
    result
  }
}

class CoordinateObserver extends ObserverProtocol{
  Object beforeInvoke( Object object, String methodName, Object[] arguments ){
    null
  }
  boolean doInvoke(){ true }
  Object afterInvoke( Object object, String methodName, Object[] arguments,
                      Object result ){
    if( object instanceof Point && ['setX', 'setY'].contains(methodName) ){
      getObservers(object).each{
        it.display("Screen updated (point subject changed coordinates).")
      }
    }
    result
  }
}

class ScreenObserver extends ObserverProtocol{
  Object beforeInvoke( Object object, String methodName, Object[] arguments ){
    null
  }
  boolean doInvoke(){ true }
  Object afterInvoke( Object object, String methodName, Object[] arguments,
                      Object result ){
    if( object instanceof Screen && methodName == 'display' ){
      getObservers(object).each{
        it.display("Screen updated (screen subject changed message).")
      }
    }
    result
  }
}

...

Code Block
import java.awt.Color

def colorObserver= new ColorObserver()
def coordinateObserver= new CoordinateObserver()
def screenObserver= new ScreenObserver()

def pointProxy= MultiInterceptorProxyMetaClass.getInstance( Point )
pointProxy.interceptors << colorObserver << coordinateObserver
                                               //multi-interception used here
pointProxy.use{

  def screenProxy= MultiInterceptorProxyMetaClass.getInstance( Screen )
  screenProxy.interceptors << screenObserver
  screenProxy.use{

    println("Creating Screen s1,s2,s3,s4,s5 and Point p")
    def s1= new Screen('s1'),
        s2= new Screen('s2'),
        s3= new Screen('s3'),
        s4= new Screen('s4'),
        s5= new Screen('s5')
    def p= new Point(5, 5, Color.blue)

    println("Creating observing relationships:")
    println(" - s1 and s2 observe color changes to p")
    println(" - s3 and s4 observe coordinate changes to p")
    println(" - s5 observes s2's and s4's display() method")

    colorObserver.addObserver(p, s1)
    colorObserver.addObserver(p, s2)
    coordinateObserver.addObserver(p, s3)
    coordinateObserver.addObserver(p, s4)
    screenObserver.addObserver(s2, s5)
    screenObserver.addObserver(s4, s5)

    println("Changing p's color:")
    p.setColor(Color.red)

    println("Changing p's x-coordinate:")
    p.setX(4)

    println("done.")
  }
}

/*output:
Creating Screen s1,s2,s3,s4,s5 and Point p
Creating observing relationships:
- s1 and s2 observe color changes to p
- s3 and s4 observe coordinate changes to p
- s5 observes s2's and s4's display() method
Changing p's color:
s1: Screen updated (point subject changed color).
s2: Screen updated (point subject changed color).
s5: Screen updated (screen subject changed message).
Changing p's x-coordinate:
s3: Screen updated (point subject changed coordinates).
s4: Screen updated (point subject changed coordinates).
s5: Screen updated (screen subject changed message).
done.
*/

Using a MultiInterceptorProxyMetaClass and

...

UninterceptedFriendlyInterceptor for the Decorator pattern

We can use more than one uninterceptable unintercepted interceptor with a proxy meta-class. A good example where this is necessary is the Decorator pattern. We re-use the MultiInterceptorProxyMetaClass from previous examples, but must write a special unintercepted interceptor, which we call an UninterceptableFriendlyInterceptorUninterceptedFriendlyInterceptor, that can be used as one of many with the MultiInterceptorProxyMetaClass.

Code Block
abstract class UninterceptedFriendlyInterceptor implements Interceptor{
  def proxy= null

  abstract Object doBefore( Object object, String methodName,
                            Object[] arguments )

  public Object beforeInvoke(Object object, String methodName,
                             Object[] arguments){
    def theInterceptors= proxy.interceptors
    proxy.interceptors= null
    def result
    try{
      result= doBefore(object, methodName, arguments) 
    }catch(Exception e){
      throw e
    }finally{
      proxy.interceptors= theInterceptors
    }
    result
  }
  abstract boolean doInvoke()

  abstract Object doAfter( Object object, String methodName,
                           Object[] arguments, Object result )

  public Object afterInvoke(Object object, String methodName,
                            Object[] arguments, Object result){
    def theInterceptors= proxy.interceptors
    proxy.interceptors= null
    try{
      result= doAfter(object, methodName, arguments, result)
    }catch(Exception e){
      throw e
    }finally{
      proxy.interceptors= theInterceptors
    }
    result
  }
}

...

Code Block
abstract class PrintDecorator extends UninterceptedFriendlyInterceptor{
  abstract Object doBefore( Object object, String methodName,
                            Object[] arguments )

  abstract Object doAfter( Object object, String methodName, Object[] arguments,
                           Object result )

  //only execute the intercepted method if it's the last class in the chain of
  //decorators around the method...
  boolean doInvoke(){ proxy.interceptors[-1] == this }
}

class NewlineDecorator extends PrintDecorator{
  int lineSizeSoFar= 0

  Object doBefore( Object object, String methodName, Object[] arguments ){
    if( methodName == 'leftShift' && arguments[0] instanceof String ){
      if( lineSizeSoFar + arguments[0].size() > 30){
        arguments[0]= '\r\n' + arguments[0]
        lineSizeSoFar= 0
      }else{
        lineSizeSoFar += arguments[0].size()
      }
    }
  }

  Object doAfter( Object object, String methodName, Object[] arguments,
                  Object result ){
    result
  }
}

class WhitespaceDecorator extends PrintDecorator{
  def prevOutput= ' '

  Object doBefore( Object object, String methodName, Object[] arguments ){
    if( methodName == 'leftShift' && arguments[0] instanceof String ){
      if( prevOutput[-1] != ' ' && prevOutput[-1] != '\n' ){
        arguments[0] = ' ' + arguments[0]
      }
    }
  }

  Object doAfter( Object object, String methodName, Object[] arguments,
                  Object result ){
    if( methodName == 'leftShift' && arguments[0] instanceof String ){
      prevOutput= arguments[0]
    }
    result
  }
}

...

Code Block
oswProxy= MultiInterceptorProxyMetaClass.getInstance( OutputStreamWriter )
[ new NewlineDecorator(),
  new WhitespaceDecorator(), //the order of these decorators is important
].each{
  it.proxy= oswProxy
  oswProxy.interceptors << it
}
oswProxy.use{
  def wtr= new OutputStreamWriter(
                   new FileOutputStream( new File('TheOutput.txt') ) )
  wtr<< "Singing in the Rain" <<
        "hello " <<
        "climate   " <<
        "hotrod" <<
        "far out and spacy" <<
        'Clementine, darling'
  wtr.close()
}

/*output file:
Singing in the Rain hello 
climate   hotrod far out and spacy 
Clementine, darling
*/