SlideShare a Scribd company logo
Have Your Cake And
Eat It Too: Meta-
Programming Java
Howard M. Lewis Ship
A Desperate Last
Ditch Effort To
Make Java Seem
Cool
Ease of Meta Programming
What is Meta-
Programming?
Code
Reuse
Without
Inheritance
Boilerplate
Meta-
Programming
Dynamic
Languages
<p>
  <input type="text" size="10" id="v1"> *
  <input type="text" size="10" id="v2">
  <button id="calc">=</button>
  <span id="result"><em>none</em></span>
</p>
<hr>
<p>
  mult() invocations: <span id="count">0</span>
</p>



var count = 0;

function mult(v1, v2) {

    $("#count").text(++count);

    return v1 * v2;
}
$(function() {
  $("#calc").click(function() {
    var v1 = parseInt($("#v1").val());
    var v2 = parseInt($("#v2").val());
    $("#result").text(mult(v1, v2));
  });
});
function |ˈfə ng k sh ən|

noun
A function, in a mathematical sense, expresses
the idea that one quantity (the argument of the
function, also known as the input) completely
determines another quantity (the value, or
the output).
function memoize(originalfn) {

    var invocationCache = {};

    return function() {
      var args = Array.prototype.slice.call(arguments);

         var priorResult = invocationCache[args];

         if (priorResult !== undefined) {
           return priorResult;
         }                              Danger!
         var result = originalfn.apply(null, arguments);

         invocationCache[args] = result;

         return result;
    };
}

mult = memoize(mult);
Have Your Cake and Eat It Too: Meta-Programming Techniques for Java
Clojure
(defn memoize
  "Returns a memoized version of a referentially transparent function. The
  memoized version of the function keeps a cache of the mapping from arguments
  to results and, when calls with the same arguments are repeated often, has
  higher performance at the expense of higher memory use."
  {:added "1.0"
   :static true}
  [f]
  (let [mem (atom {})]
    (fn [& args]
      (if-let [e (find @mem args)]
        (val e)
        (let [ret (apply f args)]
          (swap! mem assoc args ret)
          ret)))))
Python
      def memoize(function):
          cache = {}
          def decorated_function(*args):
              if args in cache:
                  return cache[args]
              else:
                  val = function(*args)
                  cache[args] = val
                  return val
          return decorated_function
                                                                                   Ruby
            module Memoize
               def memoize(name)
                  cache = {}

                        (class<<self; self; end).send(:define_method, name) do |*args|
                            unless cache.has_key?(args)
                                cache[args] = super(*args)
                            end
                            cache[args]
                        end
                        cache
                  end
            end



http://programmingzen.com/2009/05/18/memoization-in-ruby-and-python/
Java?
Uh, maybe
a Subclass?
"Closed" Language
Challenges
❝Java is C++ without the
 guns, knives, and clubs❞



James Gosling
❝JavaScript has more in
 common with functional
 languages like Lisp or
 Scheme than with C or
 Java❞


Douglas Crockford
Oracle owns this



Source                                     Executable
         Compiler    Bytecode   Hotspot
 Code                                     Native Code
AspectJ
                Source
                           Compiler   Bytecode
                 Code




                                        AspectJ Weaver
   Aspects and Pointcuts




                                                                    Executable
                                      Bytecode           Hotspot
                                                                   Native Code




http://www.eclipse.org/aspectj/
Not targeted on any specific class or method

     public abstract aspect Memoize pertarget(method()){
         HashMap cache = new HashMap();

          String makeKey(Object[] args) {
              String key = "";
              for (int i = 0; i < args.length; i++) {
                  key += args[i].toString();
                                                               The key is formed from the
                  if (i < (args.length - 1)) {                   toString() values of the
                                                                    parameters. Ick.
                       key += ",";
                  }
              }
              return key;
          }

          abstract pointcut method();

          Object around(): method() {
              String key = makeKey(thisJoinPoint.getArgs());
              if (cache.containsKey(key)) {
                  return cache.get(key);
              } else {
                  Object result = proceed();
                  cache.put(key,result);
                  return result;
              }
          }
     }
http://www.jroller.com/mschinc/entry/memoization_with_aop_and_aspectj
Extend Memoize to
              apply to specific classes &
                       methods

public aspect MemoizeFib extends Memoize {
    pointcut method(): call(int Test.fib(int));
}


                                   Identify method(s) to be
                                      targeted by Aspect
Tapestry Plastic
Rewrite simple classes on
                             Bytecode
                                                    the fly
                            Manipulation




                               Java
                              Meta-                Central code path for all
                           Programming                  instantiations
                              Trinity

                                                  Component
  Annotations
                                                  Architecture



 Identify which classes/
methods/fields to change
ASM 3.3.1
• Small & Fast
• Used in:
  • Clojure
  • Groovy
  • Hibernate
  • Spring
  • JDK
Interface
                                                    ClassVisitor




                                                                                        Byte
                        Class                                      Class
.class file                               Adapters                                     code as
                       Reader                                      Writer
                                                                                       byte[]
             Read /             Invoke                Invoke
                                                                            Produce
             Analyze              API                   API
Reader: parse bytecode, invoke
                               methods on ClassVisitor

ClassReader reader = new ClassReader("com.example.MyClass");
ClassWriter writer = new ClassWriter(reader, 0);

ClassVisitor visitor = new AddFieldAdapter(writer, ACC_PRIVATE,
  "invokeCount", "I");

reader.accept(visitor, 0);                   Adaptor: Sits between
                                               Reader & Writer
byte[] bytecode = writer.toByteArray();


                   Writer: methods construct bytecode
Most methods delegate to ClassVisitor
public class AddFieldAdapter extends ClassAdapter {
  private final int fAcc;
  private final String fName;
  private final String fDesc;

    public AddFieldAdapter(ClassVisitor cv, int fAcc, String fName, String fDesc) {
      super(cv);
      this.fAcc = fAcc;
      this.fName = fName;
      this.fDesc = fDesc;
    }

    @Override
    public void visitEnd() {
      FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null);
      if (fv != null) {
        fv.visitEnd();
      }                         "Simulate" a field read from the input class
      cv.visitEnd();                       after everything else
    }
}
Client Code    Plastic API


                      ASM



                  Total
              Encapsulation
Original                   Transformed
             Bytecode                     Bytecode
                         Plastic Class                 Transformed
.class file
                            Loader                         Class




                         PlasticClass
Standard Class    Standard
.class file
                 Loader          Class




              Plastic Class   Transformed
                 Loader           Class
Leaky




Abstractions
Standard Class                 Standard
                Loader                       Class




             Plastic Class               Transformed
                Loader                       Class



ClassCastException: org.apache.tapestry5.corelib.components.Grid
can not be cast to org.apache.tapestry5.corelib.components.Grid
Performs transformations on PlasticClass

                                                                        Plastic
                                                                       Manager
                                                                       Delegate
Which classes are transformed (by package) Plastic
Access to ClassInstantiator                Manager




                                                                Plastic
                                                             ClassLoader
public class PlasticManager {                       For instantiating existing
                                                          components
    public ClassLoader getClassLoader() { … }

    public <T> ClassInstantiator<T> getClassInstantiator(String className) { … }

    public <T> ClassInstantiator<T> createClass(Class<T> baseClass,
        PlasticClassTransformer callback) { … }

    public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType,
        PlasticClassTransformer callback) { … }

    …
}                  For creating proxies and
                        other objects


                                      public interface PlasticClassTransformer
                                      {
                                        void transform(PlasticClass plasticClass);
                                      }
public interface AnnotationAccess
{
  <T extends Annotation> boolean hasAnnotation(Class<T> annotationType);

    <T extends Annotation> T getAnnotation(Class<T> annotationType);
}




                PlasticClass      PlasticField   PlasticMethod
Extending
Methods with
Advice
Method Advice
                     public class EditUser
                     {
                       @Inject
                       private Session session;

                         @Propery
                         private User user;
 Advise method to
                         @CommitAfter
manage transaction       void onSuccessFromForm() {
      commit               session.saveOrUpdate(user);
                         }
                     }




                                              void onSuccessFromForm() {
                                                try {
                                                  session.saveOrUpdate();
                                                  commit-transaction();
                                                } catch (RuntimeException ex) {
                                                  rollback-transaction();
                                                  throw ex;
                                                }
                                              }
Introduce Method
  public interface PlasticClass {

    Set<PlasticMethod> introduceInterface(Class interfaceType);

    PlasticMethod introduceMethod(MethodDescription description);

    PlasticMethod introduceMethod(Method method);

    PlasticMethod introducePrivateMethod(String typeName,
      String suggestedName,
      String[] argumentTypes,
      String[] exceptionTypes);




  public interface PlasticMethod {

    …

    PlasticMethod addAdvice(MethodAdvice advice);
Define an annotation




 Create a worker for
   the annotation



 Apply annotation to
        class




Test transformed class
MethodInvocation
                  public interface MethodAdvice {
                    void advise(MethodInvocation invocation);
                  }


• Inspect method parameters
• Override method parameters
• Proceed
• Inspect / Override return value
• Inspect / Override thrown checked exception
public class CommitAfterWorker implements ComponentClassTransformWorker2 {
  private final HibernateSessionManager manager;

    private final MethodAdvice advice = new MethodAdvice() {          Shared Advice
       …
    };

    public CommitAfterWorker(HibernateSessionManager manager) {
      this.manager = manager;
    }

    public void transform(PlasticClass plasticClass,
                          TransformationSupport support,
                          MutableComponentModel model) {
      for (PlasticMethod method :
             plasticClass.getMethodsWithAnnotation(CommitAfter.class)) {
        method.addAdvice(advice);
      }
    }
}
                 Advice object receives control when method invoked
Traditional Java

     Compilation

                   … time passes …

                                     Runtime
With Transfomations

       Compilation

                     Transformation

                                      Runtime
MethodInvocation          Method Advice
             getParameter(int) : Object
               getInstance(): Object
                                             Advised
                     proceed()
                        …                    Method




private final MethodAdvice advice = new MethodAdvice() {
  public void advise(MethodInvocation invocation) {
    try {
      invocation.proceed();           To the method OR next   advice
             // Success or checked exception:

           manager.commit();
         } catch (RuntimeException ex) {
           manager.abort();

             throw ex;
         }
     }
};
Layering of Concerns
     MethodInvocation          Method Advice
  getParameter(int) : Object
    getInstance(): Object
                                  Advised
          proceed()
             …                    Method




                                   Logging
                                                          proceed()
                                    Security
                                                        proceed()
                                     Caching
                                                      proceed()
                                    Transactions
                                                     proceed()

                                    Advised Method
public class MemoizeAdvice implements MethodAdvice {
  private final Map<MultiKey, Object> cache = new HashMap<MultiKey, Object>();

    public void advise(MethodInvocation invocation) {
      MultiKey key = toKey(invocation);                            Not thread safe
        if (cache.containsKey(key)) {
          invocation.setReturnValue(cache.get(key));
          return;
        }

        invocation.proceed();
        invocation.rethrow();

        cache.put(key, invocation.getReturnValue());      Memory leak
    }

    private MultiKey toKey(MethodInvocation invocation) {
      Object[] params = new Object[invocation.getParameterCount()];

        for (int i = 0; i < invocation.getParameterCount(); i++) {
          params[i] = invocation.getParameter(i);
        }
                                         Assumes parameters are
        return new MultiKey(params);      immutable, implement
    }
}
                                         equals() and hashCode()
Implementing
New Methods
@Target(ElementType.TYPE)
                     @Retention(RetentionPolicy.RUNTIME)
                     @Documented
                     public @interface ImplementsEqualsHashCode {

                     }




@ImplementsEqualsHashCode
public class EqualsDemo {

    private int intValue;

    private String stringValue;

    public int getIntValue() { return intValue; }

    public void setIntValue(int intValue) { this.intValue = intValue; }

    public String getStringValue() { return stringValue; }

    public void setStringValue(String stringValue) { this.stringValue = stringValue;!   }
}
public class EqualsDemo {

    private int intValue;

    private String stringValue;

    public int getIntValue() { return intValue; }

    public void setIntValue(int intValue) { this.intValue = intValue; }

    public String getStringValue() { return stringValue; }

    public void setStringValue(String stringValue) { this.stringValue = stringValue;!   }

    public int hashCode() {
      int result = 1;

        result = 37 * result + new Integer(intValue).hashCode();
        result = 37 * result + stringValue.hashCode();

        return result;
    }
}
Worker                            public class EqualsHashCodeWorker {

                                    …
                                    Object instance = …;
                                    Object fieldValue = handle.get(instance);




@ImplementsEqualsHashCode
public class EqualsDemo {
                                                        FieldHandle
    private int intValue;                            get(Object) : Object
                                                  set(Object, Object) : void
    private String stringValue;


    …
}
public class EqualsHashCodeWorker implements PlasticClassTransformer {

    …
    public void transform(PlasticClass plasticClass) {

        if (!plasticClass.hasAnnotation(ImplementsEqualsHashCode.class)) {
          return;
        }

    …
    }
}
List<PlasticField> fields = plasticClass.getAllFields();

final List<FieldHandle> handles = new ArrayList<FieldHandle>();

for (PlasticField field : fields) {
  handles.add(field.getHandle());
}




                             FieldHandle

                          get(Object) : Object
                       set(Object, Object) : void
private MethodDescription HASHCODE = new MethodDescription("int", "hashCode");

private static final int PRIME = 37;




                  Add a new method to the class with a default empty implementation


     plasticClass.introduceMethod(HASHCODE).addAdvice(new MethodAdvice() {

       public void advise(MethodInvocation invocation) {

           …
       }
     });
public void advise(MethodInvocation invocation) {

    Object instance = invocation.getInstance();

    int result = 1;

    for (FieldHandle handle : handles) {

        Object fieldValue = handle.get(instance);

        if (fieldValue != null)
          result = (result * PRIME) + fieldValue.hashCode();
    }

    invocation.setReturnValue(result);

    // Don't proceed to the empty introduced method.
}
No Bytecode Required                                              *

                                                                    *   For you
@ImplementsEqualsHashCode
public class EqualsDemo {

    private int intValue;

    private String stringValue;

    …

    public int hashCode() {

        return 0;
    }
}

        Introduced method     public void advise(MethodInvocation invocation) {
            with default
          implementation.         invocation.setReturnValue(…);
                              }
public interface ClassInstantiator {

    T newInstance();

    <V> ClassInstantiator<T> with(Class<V> valueType,
                                  V instanceContextValue);
}



                       Pass per-instance values to the new instance


Class.forName("com.example.components.MyComponent")
  .getConstructor(ComponentConfig.class)
  .newInstance(configValue);




manager.getClassInstantiator("com.example.components.MyComponent")
  .with(ComponentConfig.class, configValue)
  .newInstance();
public class EqualsDemo {

    private int intValue;

    private String stringValue;

    …

    public boolean equals(Object other) {

        if (other == null) return false;

        if (this == other) return true;

        if (this.getClass() != other.getClass()) return false;

        EqualsDemo o = (EqualsDemo)other;

        if (intValue != o.intValue) return false;

        if (! stringValue.equals(o.stringValue)) return false;

        return true;
    }
}
private MethodDescription EQUALS = new MethodDescription("boolean",
  "equals", "java.lang.Object");




plasticClass.introduceMethod(EQUALS).addAdvice(new MethodAdvice() {

    public void advise(MethodInvocation invocation) {

        Object thisInstance = invocation.getInstance();
        Object otherInstance = invocation.getParameter(0);

        invocation.setReturnValue(isEqual(thisInstance, otherInstance));

        // Don't proceed to the empty introduced method.
    }

    private boolean isEqual(…) { … }
}
private boolean isEqual(Object thisInstance, Object otherInstance) {
  if (thisInstance == otherInstance) {
    return true;
  }

    if (otherInstance == null) {
      return false;
    }

    if (!(thisInstance.getClass() == otherInstance.getClass())) {
      return false;
    }

    for (FieldHandle handle : handles) {
      Object thisValue = handle.get(thisInstance);
      Object otherValue = handle.get(otherInstance);

        if (!(thisValue == otherValue || thisValue.equals(otherValue))) {
          return false;
        }
    }

    return true;
}
Testing with Spock
 class EqualsHashCodeTests extends Specification {

   PlasticManagerDelegate delegate =                                 The delegate manages one
     new StandardDelegate(new EqualsHashCodeWorker())                    or more workers
   PlasticManager mgr = PlasticManager.withContextClassLoader()
     .packages(["examples.plastic.transformed"])
     .delegate(delegate)
     .create();
                                  Only top-level classes in example.plastic.transformed
                                               are passed to the delegate

   ClassInstantiator instantiator = mgr.getClassInstantiator(EqualsDemo.class.name)


                                                examples.plastic.transformed.EqualsDemo




http://code.google.com/p/spock/
def "simple comparison"() {
  def instance1 = instantiator.newInstance()
  def instance2 = instantiator.newInstance()
  def instance3 = instantiator.newInstance()
  def instance4 = instantiator.newInstance()

    instance1.intValue = 99                Groovy invokes
    instance1.stringValue = "Hello"
                                          getters & setters
    instance2.intValue = 100
    instance2.stringValue = "Hello"

    instance3.intValue = 99
    instance3.stringValue = "Goodbye"

    instance4.intValue = 99
    instance4.stringValue = "Hello"

    expect:

    instance1 != instance2
                                             Groovy: ==
    instance1 != instance3                 operator invokes
                                               equals()
    instance1 == instance4
}
Creating a flexible
API
API !=
Interface
Layout.tml
<t:actionlink t:id="reset">reset session</t:actionlink>




   public class Layout {

       @Inject
       private Request request;

       void onActionFromReset() {
         request.getSession(true).invalidate();
       }
   }


         Naming convention + expected behavior == API
Using Traditional API
 public class Layout implements HypotheticalComponentAPI {

     @Inject
     private Request request;

     public void registerEventHandlers(ComponentEventRegistry registry) {

       registry.addEventHandler("action", "reset",
         new ComponentEventListener() {
           public boolean handle(ComponentEvent event) {
             request.getSession(true).invalidate();          Essence
             return true;
           }
         });
     }                           public class Layout {
 }
                                      @Inject
                                      private Request request;

                                      void onActionFromReset() {
                        Essence         request.getSession(true).invalidate();
                                      }
                                  }
public class Layout implements Component {

    @Inject
    private Request request;

    void onActionFromReset() {
      request.getSession(true).invalidate();
    }

    public boolean dispatchComponentEvent(ComponentEvent event) {
      boolean result = false;

        if (event.matches("action", "reset", 0)) {          0 is the parameter count
          onActionFromReset();
          result = true;
        }

        …                                        There's our rigid interface
        return result;
    }                     public interface Component {

    …                         boolean dispatchComponentEvent(ComponentEvent event);
}
                              …
                          }
for (PlasticMethod method : matchEventMethods(plasticClass)) {

    String eventName = toEventName(method);
    String componentId = toComponentId(method);

    MethodAdvice advice = createAdvice(eventName, componentId, method);

    PlasticMethod dispatch =   plasticClass.introduceMethod(
      TransformConstants.DISPATCH_COMPONENT_EVENT_DESCRIPTION);

    dispatch.addAdvice(advice);
}
private MethodAdvice createAdvice(final String eventName,
    final String componentId,
    PlasticMethod method) {
  final MethodHandle handle = method.getHandle();

    return new MethodAdvice() {
      public void advise(MethodInvocation invocation) {

     invocation.proceed();           Invoke default or super-class implementation first
         ComponentEvent event = (ComponentEvent) invocation.getParameter(0);

         if (event.matches(eventName, componentId, 0)) {
           handle.invoke(invocation.getInstance());
           invocation.rethrow();
                                                      Simplification – real code handles
             invocation.setReturnValue(true);
         }                                               methods with parameters
    };
}
Meta-Programming
Fields
/**
 * Identifies a field that may not store the value null.
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {

}
FieldConduit
private String value;     get (Object, InstanceContext) : Object
                        set(Object, InstanceContext, Object) : void
public class NullCheckingConduit implements FieldConduit<Object> {

  private final String className;

  private final String fieldName;

 private Object fieldValue;           Replaces actual field
  private NullCheckingConduit(String className, String fieldName) {
    this.className = className;
    this.fieldName = fieldName;
  }

  public Object get(Object instance, InstanceContext context) {
    return fieldValue;
  }

  public void set(Object instance, InstanceContext context, Object newValue) {

      if (newValue == null)
        throw new IllegalArgumentException(String.format(
            "Field %s of class %s may not be assigned null.",
            fieldName, className));

      fieldValue = newValue;
  }
field.setConduit(new NullCheckingConduit(className, fieldName));




                                                               FieldConduit

                                                  get (Object, InstanceContext) : Object
           Instance 1                           set(Object, InstanceContext, Object) : void




                                                            FieldConduit
           Instance 2
                                               private Object fieldValue;

                                                                               Shared!

           Instance 3
@SuppressWarnings({"unchecked"})
public void transform(PlasticClass plasticClass) {
  for (PlasticField field : plasticClass
          .getFieldsWithAnnotation(NotNull.class)) {

        final String className = plasticClass.getClassName();
        final String fieldName = field.getName();

        field.setComputedConduit(new ComputedValue() {

          public Object get(InstanceContext context) {
            return new NullCheckingConduit(className, fieldName);
          }
        });
    }
}                   ComputedValue: A Factory that is executed inside the
                            transformed class' constructor
class NotNullTests extends Specification {

  …

  def "store null is failure"() {

      def o = instantiator.newInstance()

      when:

      o.value = null

      then:

      def e = thrown(IllegalArgumentException)

    e.message == "Field value of class
examples.plastic.transformed.NotNullDemo may not be assigned null."
  }
}
Private Fields Only
package examples.plastic.transformed;

import examples.plastic.annotations.NotNull;

public class NotNullDemo {

    @NotNull
    public String value;

}



java.lang.IllegalArgumentException: Field value of class examples.plastic.transformed.NotNullDemo is
not private. Class transformation requires that all instance fields be private.
  at org.apache.tapestry5.internal.plastic.PlasticClassImpl.<init>()
  at org.apache.tapestry5.internal.plastic.PlasticClassPool.createTransformation()
  at org.apache.tapestry5.internal.plastic.PlasticClassPool.getPlasticClassTransformation()
  at org.apache.tapestry5.internal.plastic.PlasticClassPool.loadAndTransformClass()
  at org.apache.tapestry5.internal.plastic.PlasticClassLoader.loadClass()
  at java.lang.ClassLoader.loadClass()
  at org.apache.tapestry5.internal.plastic.PlasticClassPool.getClassInstantiator()
  at org.apache.tapestry5.plastic.PlasticManager.getClassInstantiator()
  at examples.plastic.PlasticDemosSpecification.createInstantiator()
  at examples.plastic.NotNullTests.setup()
package examples.plastic.transformed;

import examples.plastic.annotations.NotNull;

public class NotNullDemo {

    @NotNull
    private String value;

    public String getValue() {
      return get_value(); // was return value;
    }

    public void setValue(String value) {
      set_value(value); // was this.value = value;
    }

    …
}
package examples.plastic.transformed;

import examples.plastic.annotations.NotNull;

public class NotNullDemo {
  …

    private final InstanceContext ic;

    private final FieldConduit valueConduit;

    public NotNullDemo(StaticContext sc, InstanceContext ic) {
      this.ic = ic;
      valueConduit = (FieldConduit) ((ComputedValue) sc.get(0)).get(ic);
    }

    String get_value() { return (String) valueConduit.get(this, ic); }

    void set_value(String newValue) {
      valueConduit.set(this, ic, newValue);
    }
}
No More new
Conclusion
Bytecode
               Manipulation




                  Java
                 Meta-
              Programming
                 Trinity

                              Component
Annotations
                              Architecture
Structure
• Best With Managed Lifecycle
  • new no longer allowed
  • Instantiation by class name
• Framework / Code Interactions via Interface(s)
  • Modify components to implement Interface(s)
• Blurs lines between compile & runtime
Not Covered
• Field injection
• Direct bytecode builder API

More Related Content

What's hot (16)

ODP
Method Handles in Java
hendersk
 
PPTX
Java 10, Java 11 and beyond
Rafael Winterhalter
 
PPTX
NIO and NIO2
Balamurugan Soundararajan
 
PDF
Java Cheat Sheet
GlowTouch
 
PDF
JVM Mechanics: When Does the JVM JIT & Deoptimize?
Doug Hawkins
 
PPT
Core java
kasaragaddaslide
 
PPT
Java basic tutorial by sanjeevini india
Sanjeev Tripathi
 
PPT
Invoke dynamics
Balamurugan Soundararajan
 
PPTX
The Java memory model made easy
Rafael Winterhalter
 
PPTX
Java and OpenJDK: disecting the ecosystem
Rafael Winterhalter
 
KEY
Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...
Guillaume Laforge
 
PDF
Java Programming Guide Quick Reference
FrescatiStory
 
PDF
03 - Qt UI Development
Andreas Jakl
 
PPTX
Playing with Java Classes and Bytecode
Yoav Avrahami
 
PPSX
Java Tutorial
Akash Pandey
 
PPTX
The definitive guide to java agents
Rafael Winterhalter
 
Method Handles in Java
hendersk
 
Java 10, Java 11 and beyond
Rafael Winterhalter
 
Java Cheat Sheet
GlowTouch
 
JVM Mechanics: When Does the JVM JIT & Deoptimize?
Doug Hawkins
 
Core java
kasaragaddaslide
 
Java basic tutorial by sanjeevini india
Sanjeev Tripathi
 
Invoke dynamics
Balamurugan Soundararajan
 
The Java memory model made easy
Rafael Winterhalter
 
Java and OpenJDK: disecting the ecosystem
Rafael Winterhalter
 
Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...
Guillaume Laforge
 
Java Programming Guide Quick Reference
FrescatiStory
 
03 - Qt UI Development
Andreas Jakl
 
Playing with Java Classes and Bytecode
Yoav Avrahami
 
Java Tutorial
Akash Pandey
 
The definitive guide to java agents
Rafael Winterhalter
 

Viewers also liked (6)

PDF
Codemash-Tapestry.pdf
Howard Lewis Ship
 
PDF
Modern Application Foundations: Underscore and Twitter Bootstrap
Howard Lewis Ship
 
PDF
Arduino: Open Source Hardware Hacking from the Software Nerd Perspective
Howard Lewis Ship
 
PDF
Backbone.js: Run your Application Inside The Browser
Howard Lewis Ship
 
PDF
Testing Web Applications with GEB
Howard Lewis Ship
 
PDF
Spock: A Highly Logical Way To Test
Howard Lewis Ship
 
Codemash-Tapestry.pdf
Howard Lewis Ship
 
Modern Application Foundations: Underscore and Twitter Bootstrap
Howard Lewis Ship
 
Arduino: Open Source Hardware Hacking from the Software Nerd Perspective
Howard Lewis Ship
 
Backbone.js: Run your Application Inside The Browser
Howard Lewis Ship
 
Testing Web Applications with GEB
Howard Lewis Ship
 
Spock: A Highly Logical Way To Test
Howard Lewis Ship
 
Ad

Similar to Have Your Cake and Eat It Too: Meta-Programming Techniques for Java (20)

ODP
I Know Kung Fu - Juggling Java Bytecode
Alexander Shopov
 
KEY
High Performance Ruby - Golden Gate RubyConf 2012
Charles Nutter
 
PDF
A/F/C-orientation
Sosuke MORIGUCHI
 
PDF
Refactoring In Tdd The Missing Part
Gabriele Lana
 
PDF
Atlassian Groovy Plugins
Paul King
 
PDF
Better Software: introduction to good code
Giordano Scalzo
 
PDF
Logic-based program transformation in symbiosis with Eclipse
Coen De Roover
 
KEY
Groovy: to Infinity and Beyond -- JavaOne 2010 -- Guillaume Laforge
Guillaume Laforge
 
PPT
Fast Forward To Scala
Martin Kneissl
 
PDF
Clojure - A new Lisp
elliando dias
 
PDF
Java beans
Mukesh Tekwani
 
PPTX
Mastering Java Bytecode With ASM - 33rd degree, 2012
Anton Arhipov
 
PDF
Jvm internals
Luiz Fernando Teston
 
PDF
Introduction to Scala
Brian Hsu
 
PDF
Building Atlassian Plugins with Groovy - Atlassian Summit 2010 - Lightning Talks
Atlassian
 
PDF
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Pau...
Paul King
 
PPTX
Metaprogramming Techniques In Groovy And Grails
zenMonkey
 
PDF
JRuby @ Boulder Ruby
Nick Sieger
 
PDF
Annotations in PHP: They Exist
Rafael Dohms
 
I Know Kung Fu - Juggling Java Bytecode
Alexander Shopov
 
High Performance Ruby - Golden Gate RubyConf 2012
Charles Nutter
 
A/F/C-orientation
Sosuke MORIGUCHI
 
Refactoring In Tdd The Missing Part
Gabriele Lana
 
Atlassian Groovy Plugins
Paul King
 
Better Software: introduction to good code
Giordano Scalzo
 
Logic-based program transformation in symbiosis with Eclipse
Coen De Roover
 
Groovy: to Infinity and Beyond -- JavaOne 2010 -- Guillaume Laforge
Guillaume Laforge
 
Fast Forward To Scala
Martin Kneissl
 
Clojure - A new Lisp
elliando dias
 
Java beans
Mukesh Tekwani
 
Mastering Java Bytecode With ASM - 33rd degree, 2012
Anton Arhipov
 
Jvm internals
Luiz Fernando Teston
 
Introduction to Scala
Brian Hsu
 
Building Atlassian Plugins with Groovy - Atlassian Summit 2010 - Lightning Talks
Atlassian
 
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Pau...
Paul King
 
Metaprogramming Techniques In Groovy And Grails
zenMonkey
 
JRuby @ Boulder Ruby
Nick Sieger
 
Annotations in PHP: They Exist
Rafael Dohms
 
Ad

More from Howard Lewis Ship (11)

PDF
Clojure: Towards The Essence Of Programming (What's Next? Conference, May 2011)
Howard Lewis Ship
 
PDF
Practical Clojure Programming
Howard Lewis Ship
 
PDF
Clojure: Towards The Essence of Programming
Howard Lewis Ship
 
PDF
Codemash-Clojure.pdf
Howard Lewis Ship
 
PDF
Tapestry 5: Java Power, Scripting Ease
Howard Lewis Ship
 
PDF
Brew up a Rich Web Application with Cappuccino
Howard Lewis Ship
 
PDF
Clojure Deep Dive
Howard Lewis Ship
 
PDF
Clojure: Functional Concurrency for the JVM (presented at OSCON)
Howard Lewis Ship
 
PDF
Cascade
Howard Lewis Ship
 
PDF
Tapestry: State of the Union
Howard Lewis Ship
 
ZIP
Clojure: Functional Concurrency for the JVM (presented at Open Source Bridge)
Howard Lewis Ship
 
Clojure: Towards The Essence Of Programming (What's Next? Conference, May 2011)
Howard Lewis Ship
 
Practical Clojure Programming
Howard Lewis Ship
 
Clojure: Towards The Essence of Programming
Howard Lewis Ship
 
Codemash-Clojure.pdf
Howard Lewis Ship
 
Tapestry 5: Java Power, Scripting Ease
Howard Lewis Ship
 
Brew up a Rich Web Application with Cappuccino
Howard Lewis Ship
 
Clojure Deep Dive
Howard Lewis Ship
 
Clojure: Functional Concurrency for the JVM (presented at OSCON)
Howard Lewis Ship
 
Tapestry: State of the Union
Howard Lewis Ship
 
Clojure: Functional Concurrency for the JVM (presented at Open Source Bridge)
Howard Lewis Ship
 

Recently uploaded (20)

PPTX
OpenID AuthZEN - Analyst Briefing July 2025
David Brossard
 
PDF
POV_ Why Enterprises Need to Find Value in ZERO.pdf
darshakparmar
 
PDF
New from BookNet Canada for 2025: BNC BiblioShare - Tech Forum 2025
BookNet Canada
 
PDF
Agentic AI lifecycle for Enterprise Hyper-Automation
Debmalya Biswas
 
PDF
Jak MŚP w Europie Środkowo-Wschodniej odnajdują się w świecie AI
dominikamizerska1
 
PDF
What Makes Contify’s News API Stand Out: Key Features at a Glance
Contify
 
PPTX
From Sci-Fi to Reality: Exploring AI Evolution
Svetlana Meissner
 
PDF
Newgen Beyond Frankenstein_Build vs Buy_Digital_version.pdf
darshakparmar
 
PPTX
Future Tech Innovations 2025 – A TechLists Insight
TechLists
 
PDF
"AI Transformation: Directions and Challenges", Pavlo Shaternik
Fwdays
 
PDF
Reverse Engineering of Security Products: Developing an Advanced Microsoft De...
nwbxhhcyjv
 
PDF
The Rise of AI and IoT in Mobile App Tech.pdf
IMG Global Infotech
 
PDF
DevBcn - Building 10x Organizations Using Modern Productivity Metrics
Justin Reock
 
PDF
Staying Human in a Machine- Accelerated World
Catalin Jora
 
PDF
Empower Inclusion Through Accessible Java Applications
Ana-Maria Mihalceanu
 
PDF
Achieving Consistent and Reliable AI Code Generation - Medusa AI
medusaaico
 
PPTX
AI Penetration Testing Essentials: A Cybersecurity Guide for 2025
defencerabbit Team
 
PPTX
COMPARISON OF RASTER ANALYSIS TOOLS OF QGIS AND ARCGIS
Sharanya Sarkar
 
PPTX
WooCommerce Workshop: Bring Your Laptop
Laura Hartwig
 
DOCX
Python coding for beginners !! Start now!#
Rajni Bhardwaj Grover
 
OpenID AuthZEN - Analyst Briefing July 2025
David Brossard
 
POV_ Why Enterprises Need to Find Value in ZERO.pdf
darshakparmar
 
New from BookNet Canada for 2025: BNC BiblioShare - Tech Forum 2025
BookNet Canada
 
Agentic AI lifecycle for Enterprise Hyper-Automation
Debmalya Biswas
 
Jak MŚP w Europie Środkowo-Wschodniej odnajdują się w świecie AI
dominikamizerska1
 
What Makes Contify’s News API Stand Out: Key Features at a Glance
Contify
 
From Sci-Fi to Reality: Exploring AI Evolution
Svetlana Meissner
 
Newgen Beyond Frankenstein_Build vs Buy_Digital_version.pdf
darshakparmar
 
Future Tech Innovations 2025 – A TechLists Insight
TechLists
 
"AI Transformation: Directions and Challenges", Pavlo Shaternik
Fwdays
 
Reverse Engineering of Security Products: Developing an Advanced Microsoft De...
nwbxhhcyjv
 
The Rise of AI and IoT in Mobile App Tech.pdf
IMG Global Infotech
 
DevBcn - Building 10x Organizations Using Modern Productivity Metrics
Justin Reock
 
Staying Human in a Machine- Accelerated World
Catalin Jora
 
Empower Inclusion Through Accessible Java Applications
Ana-Maria Mihalceanu
 
Achieving Consistent and Reliable AI Code Generation - Medusa AI
medusaaico
 
AI Penetration Testing Essentials: A Cybersecurity Guide for 2025
defencerabbit Team
 
COMPARISON OF RASTER ANALYSIS TOOLS OF QGIS AND ARCGIS
Sharanya Sarkar
 
WooCommerce Workshop: Bring Your Laptop
Laura Hartwig
 
Python coding for beginners !! Start now!#
Rajni Bhardwaj Grover
 

Have Your Cake and Eat It Too: Meta-Programming Techniques for Java

  • 1. Have Your Cake And Eat It Too: Meta- Programming Java Howard M. Lewis Ship
  • 2. A Desperate Last Ditch Effort To Make Java Seem Cool
  • 3. Ease of Meta Programming
  • 9. <p> <input type="text" size="10" id="v1"> * <input type="text" size="10" id="v2"> <button id="calc">=</button> <span id="result"><em>none</em></span> </p> <hr> <p> mult() invocations: <span id="count">0</span> </p> var count = 0; function mult(v1, v2) { $("#count").text(++count); return v1 * v2; }
  • 10. $(function() { $("#calc").click(function() { var v1 = parseInt($("#v1").val()); var v2 = parseInt($("#v2").val()); $("#result").text(mult(v1, v2)); }); });
  • 11. function |ˈfə ng k sh ən| noun A function, in a mathematical sense, expresses the idea that one quantity (the argument of the function, also known as the input) completely determines another quantity (the value, or the output).
  • 12. function memoize(originalfn) { var invocationCache = {}; return function() { var args = Array.prototype.slice.call(arguments); var priorResult = invocationCache[args]; if (priorResult !== undefined) { return priorResult; } Danger! var result = originalfn.apply(null, arguments); invocationCache[args] = result; return result; }; } mult = memoize(mult);
  • 14. Clojure (defn memoize "Returns a memoized version of a referentially transparent function. The memoized version of the function keeps a cache of the mapping from arguments to results and, when calls with the same arguments are repeated often, has higher performance at the expense of higher memory use." {:added "1.0" :static true} [f] (let [mem (atom {})] (fn [& args] (if-let [e (find @mem args)] (val e) (let [ret (apply f args)] (swap! mem assoc args ret) ret)))))
  • 15. Python def memoize(function): cache = {} def decorated_function(*args): if args in cache: return cache[args] else: val = function(*args) cache[args] = val return val return decorated_function Ruby module Memoize def memoize(name) cache = {} (class<<self; self; end).send(:define_method, name) do |*args| unless cache.has_key?(args) cache[args] = super(*args) end cache[args] end cache end end http://programmingzen.com/2009/05/18/memoization-in-ruby-and-python/
  • 16. Java?
  • 19. ❝Java is C++ without the guns, knives, and clubs❞ James Gosling
  • 20. ❝JavaScript has more in common with functional languages like Lisp or Scheme than with C or Java❞ Douglas Crockford
  • 21. Oracle owns this Source Executable Compiler Bytecode Hotspot Code Native Code
  • 22. AspectJ Source Compiler Bytecode Code AspectJ Weaver Aspects and Pointcuts Executable Bytecode Hotspot Native Code http://www.eclipse.org/aspectj/
  • 23. Not targeted on any specific class or method public abstract aspect Memoize pertarget(method()){ HashMap cache = new HashMap(); String makeKey(Object[] args) { String key = ""; for (int i = 0; i < args.length; i++) { key += args[i].toString(); The key is formed from the if (i < (args.length - 1)) { toString() values of the parameters. Ick. key += ","; } } return key; } abstract pointcut method(); Object around(): method() { String key = makeKey(thisJoinPoint.getArgs()); if (cache.containsKey(key)) { return cache.get(key); } else { Object result = proceed(); cache.put(key,result); return result; } } } http://www.jroller.com/mschinc/entry/memoization_with_aop_and_aspectj
  • 24. Extend Memoize to apply to specific classes & methods public aspect MemoizeFib extends Memoize { pointcut method(): call(int Test.fib(int)); } Identify method(s) to be targeted by Aspect
  • 26. Rewrite simple classes on Bytecode the fly Manipulation Java Meta- Central code path for all Programming instantiations Trinity Component Annotations Architecture Identify which classes/ methods/fields to change
  • 27. ASM 3.3.1 • Small & Fast • Used in: • Clojure • Groovy • Hibernate • Spring • JDK
  • 28. Interface ClassVisitor Byte Class Class .class file Adapters code as Reader Writer byte[] Read / Invoke Invoke Produce Analyze API API
  • 29. Reader: parse bytecode, invoke methods on ClassVisitor ClassReader reader = new ClassReader("com.example.MyClass"); ClassWriter writer = new ClassWriter(reader, 0); ClassVisitor visitor = new AddFieldAdapter(writer, ACC_PRIVATE, "invokeCount", "I"); reader.accept(visitor, 0); Adaptor: Sits between Reader & Writer byte[] bytecode = writer.toByteArray(); Writer: methods construct bytecode
  • 30. Most methods delegate to ClassVisitor public class AddFieldAdapter extends ClassAdapter { private final int fAcc; private final String fName; private final String fDesc; public AddFieldAdapter(ClassVisitor cv, int fAcc, String fName, String fDesc) { super(cv); this.fAcc = fAcc; this.fName = fName; this.fDesc = fDesc; } @Override public void visitEnd() { FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null); if (fv != null) { fv.visitEnd(); } "Simulate" a field read from the input class cv.visitEnd(); after everything else } }
  • 31. Client Code Plastic API ASM Total Encapsulation
  • 32. Original Transformed Bytecode Bytecode Plastic Class Transformed .class file Loader Class PlasticClass
  • 33. Standard Class Standard .class file Loader Class Plastic Class Transformed Loader Class
  • 35. Standard Class Standard Loader Class Plastic Class Transformed Loader Class ClassCastException: org.apache.tapestry5.corelib.components.Grid can not be cast to org.apache.tapestry5.corelib.components.Grid
  • 36. Performs transformations on PlasticClass Plastic Manager Delegate Which classes are transformed (by package) Plastic Access to ClassInstantiator Manager Plastic ClassLoader
  • 37. public class PlasticManager { For instantiating existing components public ClassLoader getClassLoader() { … } public <T> ClassInstantiator<T> getClassInstantiator(String className) { … } public <T> ClassInstantiator<T> createClass(Class<T> baseClass, PlasticClassTransformer callback) { … } public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, PlasticClassTransformer callback) { … } … } For creating proxies and other objects public interface PlasticClassTransformer { void transform(PlasticClass plasticClass); }
  • 38. public interface AnnotationAccess { <T extends Annotation> boolean hasAnnotation(Class<T> annotationType); <T extends Annotation> T getAnnotation(Class<T> annotationType); } PlasticClass PlasticField PlasticMethod
  • 40. Method Advice public class EditUser { @Inject private Session session; @Propery private User user; Advise method to @CommitAfter manage transaction void onSuccessFromForm() { commit session.saveOrUpdate(user); } } void onSuccessFromForm() { try { session.saveOrUpdate(); commit-transaction(); } catch (RuntimeException ex) { rollback-transaction(); throw ex; } }
  • 41. Introduce Method public interface PlasticClass { Set<PlasticMethod> introduceInterface(Class interfaceType); PlasticMethod introduceMethod(MethodDescription description); PlasticMethod introduceMethod(Method method); PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes, String[] exceptionTypes); public interface PlasticMethod { … PlasticMethod addAdvice(MethodAdvice advice);
  • 42. Define an annotation Create a worker for the annotation Apply annotation to class Test transformed class
  • 43. MethodInvocation public interface MethodAdvice { void advise(MethodInvocation invocation); } • Inspect method parameters • Override method parameters • Proceed • Inspect / Override return value • Inspect / Override thrown checked exception
  • 44. public class CommitAfterWorker implements ComponentClassTransformWorker2 { private final HibernateSessionManager manager; private final MethodAdvice advice = new MethodAdvice() { Shared Advice … }; public CommitAfterWorker(HibernateSessionManager manager) { this.manager = manager; } public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model) { for (PlasticMethod method : plasticClass.getMethodsWithAnnotation(CommitAfter.class)) { method.addAdvice(advice); } } } Advice object receives control when method invoked
  • 45. Traditional Java Compilation … time passes … Runtime
  • 46. With Transfomations Compilation Transformation Runtime
  • 47. MethodInvocation Method Advice getParameter(int) : Object getInstance(): Object Advised proceed() … Method private final MethodAdvice advice = new MethodAdvice() { public void advise(MethodInvocation invocation) { try { invocation.proceed(); To the method OR next advice // Success or checked exception: manager.commit(); } catch (RuntimeException ex) { manager.abort(); throw ex; } } };
  • 48. Layering of Concerns MethodInvocation Method Advice getParameter(int) : Object getInstance(): Object Advised proceed() … Method Logging proceed() Security proceed() Caching proceed() Transactions proceed() Advised Method
  • 49. public class MemoizeAdvice implements MethodAdvice { private final Map<MultiKey, Object> cache = new HashMap<MultiKey, Object>(); public void advise(MethodInvocation invocation) { MultiKey key = toKey(invocation); Not thread safe if (cache.containsKey(key)) { invocation.setReturnValue(cache.get(key)); return; } invocation.proceed(); invocation.rethrow(); cache.put(key, invocation.getReturnValue()); Memory leak } private MultiKey toKey(MethodInvocation invocation) { Object[] params = new Object[invocation.getParameterCount()]; for (int i = 0; i < invocation.getParameterCount(); i++) { params[i] = invocation.getParameter(i); } Assumes parameters are return new MultiKey(params); immutable, implement } } equals() and hashCode()
  • 51. @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ImplementsEqualsHashCode { } @ImplementsEqualsHashCode public class EqualsDemo { private int intValue; private String stringValue; public int getIntValue() { return intValue; } public void setIntValue(int intValue) { this.intValue = intValue; } public String getStringValue() { return stringValue; } public void setStringValue(String stringValue) { this.stringValue = stringValue;! } }
  • 52. public class EqualsDemo { private int intValue; private String stringValue; public int getIntValue() { return intValue; } public void setIntValue(int intValue) { this.intValue = intValue; } public String getStringValue() { return stringValue; } public void setStringValue(String stringValue) { this.stringValue = stringValue;! } public int hashCode() { int result = 1; result = 37 * result + new Integer(intValue).hashCode(); result = 37 * result + stringValue.hashCode(); return result; } }
  • 53. Worker public class EqualsHashCodeWorker { … Object instance = …; Object fieldValue = handle.get(instance); @ImplementsEqualsHashCode public class EqualsDemo { FieldHandle private int intValue; get(Object) : Object set(Object, Object) : void private String stringValue; … }
  • 54. public class EqualsHashCodeWorker implements PlasticClassTransformer { … public void transform(PlasticClass plasticClass) { if (!plasticClass.hasAnnotation(ImplementsEqualsHashCode.class)) { return; } … } }
  • 55. List<PlasticField> fields = plasticClass.getAllFields(); final List<FieldHandle> handles = new ArrayList<FieldHandle>(); for (PlasticField field : fields) { handles.add(field.getHandle()); } FieldHandle get(Object) : Object set(Object, Object) : void
  • 56. private MethodDescription HASHCODE = new MethodDescription("int", "hashCode"); private static final int PRIME = 37; Add a new method to the class with a default empty implementation plasticClass.introduceMethod(HASHCODE).addAdvice(new MethodAdvice() { public void advise(MethodInvocation invocation) { … } });
  • 57. public void advise(MethodInvocation invocation) { Object instance = invocation.getInstance(); int result = 1; for (FieldHandle handle : handles) { Object fieldValue = handle.get(instance); if (fieldValue != null) result = (result * PRIME) + fieldValue.hashCode(); } invocation.setReturnValue(result); // Don't proceed to the empty introduced method. }
  • 58. No Bytecode Required * * For you @ImplementsEqualsHashCode public class EqualsDemo { private int intValue; private String stringValue; … public int hashCode() { return 0; } } Introduced method public void advise(MethodInvocation invocation) { with default implementation. invocation.setReturnValue(…); }
  • 59. public interface ClassInstantiator { T newInstance(); <V> ClassInstantiator<T> with(Class<V> valueType, V instanceContextValue); } Pass per-instance values to the new instance Class.forName("com.example.components.MyComponent") .getConstructor(ComponentConfig.class) .newInstance(configValue); manager.getClassInstantiator("com.example.components.MyComponent") .with(ComponentConfig.class, configValue) .newInstance();
  • 60. public class EqualsDemo { private int intValue; private String stringValue; … public boolean equals(Object other) { if (other == null) return false; if (this == other) return true; if (this.getClass() != other.getClass()) return false; EqualsDemo o = (EqualsDemo)other; if (intValue != o.intValue) return false; if (! stringValue.equals(o.stringValue)) return false; return true; } }
  • 61. private MethodDescription EQUALS = new MethodDescription("boolean", "equals", "java.lang.Object"); plasticClass.introduceMethod(EQUALS).addAdvice(new MethodAdvice() { public void advise(MethodInvocation invocation) { Object thisInstance = invocation.getInstance(); Object otherInstance = invocation.getParameter(0); invocation.setReturnValue(isEqual(thisInstance, otherInstance)); // Don't proceed to the empty introduced method. } private boolean isEqual(…) { … } }
  • 62. private boolean isEqual(Object thisInstance, Object otherInstance) { if (thisInstance == otherInstance) { return true; } if (otherInstance == null) { return false; } if (!(thisInstance.getClass() == otherInstance.getClass())) { return false; } for (FieldHandle handle : handles) { Object thisValue = handle.get(thisInstance); Object otherValue = handle.get(otherInstance); if (!(thisValue == otherValue || thisValue.equals(otherValue))) { return false; } } return true; }
  • 63. Testing with Spock class EqualsHashCodeTests extends Specification { PlasticManagerDelegate delegate = The delegate manages one new StandardDelegate(new EqualsHashCodeWorker()) or more workers PlasticManager mgr = PlasticManager.withContextClassLoader() .packages(["examples.plastic.transformed"]) .delegate(delegate) .create(); Only top-level classes in example.plastic.transformed are passed to the delegate ClassInstantiator instantiator = mgr.getClassInstantiator(EqualsDemo.class.name) examples.plastic.transformed.EqualsDemo http://code.google.com/p/spock/
  • 64. def "simple comparison"() { def instance1 = instantiator.newInstance() def instance2 = instantiator.newInstance() def instance3 = instantiator.newInstance() def instance4 = instantiator.newInstance() instance1.intValue = 99 Groovy invokes instance1.stringValue = "Hello" getters & setters instance2.intValue = 100 instance2.stringValue = "Hello" instance3.intValue = 99 instance3.stringValue = "Goodbye" instance4.intValue = 99 instance4.stringValue = "Hello" expect: instance1 != instance2 Groovy: == instance1 != instance3 operator invokes equals() instance1 == instance4 }
  • 67. Layout.tml <t:actionlink t:id="reset">reset session</t:actionlink> public class Layout { @Inject private Request request; void onActionFromReset() { request.getSession(true).invalidate(); } } Naming convention + expected behavior == API
  • 68. Using Traditional API public class Layout implements HypotheticalComponentAPI { @Inject private Request request; public void registerEventHandlers(ComponentEventRegistry registry) { registry.addEventHandler("action", "reset", new ComponentEventListener() { public boolean handle(ComponentEvent event) { request.getSession(true).invalidate(); Essence return true; } }); } public class Layout { } @Inject private Request request; void onActionFromReset() { Essence request.getSession(true).invalidate(); } }
  • 69. public class Layout implements Component { @Inject private Request request; void onActionFromReset() { request.getSession(true).invalidate(); } public boolean dispatchComponentEvent(ComponentEvent event) { boolean result = false; if (event.matches("action", "reset", 0)) { 0 is the parameter count onActionFromReset(); result = true; } … There's our rigid interface return result; } public interface Component { … boolean dispatchComponentEvent(ComponentEvent event); } … }
  • 70. for (PlasticMethod method : matchEventMethods(plasticClass)) { String eventName = toEventName(method); String componentId = toComponentId(method); MethodAdvice advice = createAdvice(eventName, componentId, method); PlasticMethod dispatch = plasticClass.introduceMethod( TransformConstants.DISPATCH_COMPONENT_EVENT_DESCRIPTION); dispatch.addAdvice(advice); }
  • 71. private MethodAdvice createAdvice(final String eventName, final String componentId, PlasticMethod method) { final MethodHandle handle = method.getHandle(); return new MethodAdvice() { public void advise(MethodInvocation invocation) { invocation.proceed(); Invoke default or super-class implementation first ComponentEvent event = (ComponentEvent) invocation.getParameter(0); if (event.matches(eventName, componentId, 0)) { handle.invoke(invocation.getInstance()); invocation.rethrow(); Simplification – real code handles invocation.setReturnValue(true); } methods with parameters }; }
  • 73. /** * Identifies a field that may not store the value null. * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface NotNull { }
  • 74. FieldConduit private String value; get (Object, InstanceContext) : Object set(Object, InstanceContext, Object) : void
  • 75. public class NullCheckingConduit implements FieldConduit<Object> { private final String className; private final String fieldName; private Object fieldValue; Replaces actual field private NullCheckingConduit(String className, String fieldName) { this.className = className; this.fieldName = fieldName; } public Object get(Object instance, InstanceContext context) { return fieldValue; } public void set(Object instance, InstanceContext context, Object newValue) { if (newValue == null) throw new IllegalArgumentException(String.format( "Field %s of class %s may not be assigned null.", fieldName, className)); fieldValue = newValue; }
  • 76. field.setConduit(new NullCheckingConduit(className, fieldName)); FieldConduit get (Object, InstanceContext) : Object Instance 1 set(Object, InstanceContext, Object) : void FieldConduit Instance 2 private Object fieldValue; Shared! Instance 3
  • 77. @SuppressWarnings({"unchecked"}) public void transform(PlasticClass plasticClass) { for (PlasticField field : plasticClass .getFieldsWithAnnotation(NotNull.class)) { final String className = plasticClass.getClassName(); final String fieldName = field.getName(); field.setComputedConduit(new ComputedValue() { public Object get(InstanceContext context) { return new NullCheckingConduit(className, fieldName); } }); } } ComputedValue: A Factory that is executed inside the transformed class' constructor
  • 78. class NotNullTests extends Specification { … def "store null is failure"() { def o = instantiator.newInstance() when: o.value = null then: def e = thrown(IllegalArgumentException) e.message == "Field value of class examples.plastic.transformed.NotNullDemo may not be assigned null." } }
  • 79. Private Fields Only package examples.plastic.transformed; import examples.plastic.annotations.NotNull; public class NotNullDemo { @NotNull public String value; } java.lang.IllegalArgumentException: Field value of class examples.plastic.transformed.NotNullDemo is not private. Class transformation requires that all instance fields be private. at org.apache.tapestry5.internal.plastic.PlasticClassImpl.<init>() at org.apache.tapestry5.internal.plastic.PlasticClassPool.createTransformation() at org.apache.tapestry5.internal.plastic.PlasticClassPool.getPlasticClassTransformation() at org.apache.tapestry5.internal.plastic.PlasticClassPool.loadAndTransformClass() at org.apache.tapestry5.internal.plastic.PlasticClassLoader.loadClass() at java.lang.ClassLoader.loadClass() at org.apache.tapestry5.internal.plastic.PlasticClassPool.getClassInstantiator() at org.apache.tapestry5.plastic.PlasticManager.getClassInstantiator() at examples.plastic.PlasticDemosSpecification.createInstantiator() at examples.plastic.NotNullTests.setup()
  • 80. package examples.plastic.transformed; import examples.plastic.annotations.NotNull; public class NotNullDemo { @NotNull private String value; public String getValue() { return get_value(); // was return value; } public void setValue(String value) { set_value(value); // was this.value = value; } … }
  • 81. package examples.plastic.transformed; import examples.plastic.annotations.NotNull; public class NotNullDemo { … private final InstanceContext ic; private final FieldConduit valueConduit; public NotNullDemo(StaticContext sc, InstanceContext ic) { this.ic = ic; valueConduit = (FieldConduit) ((ComputedValue) sc.get(0)).get(ic); } String get_value() { return (String) valueConduit.get(this, ic); } void set_value(String newValue) { valueConduit.set(this, ic, newValue); } }
  • 84. Bytecode Manipulation Java Meta- Programming Trinity Component Annotations Architecture
  • 85. Structure • Best With Managed Lifecycle • new no longer allowed • Instantiation by class name • Framework / Code Interactions via Interface(s) • Modify components to implement Interface(s) • Blurs lines between compile & runtime
  • 86. Not Covered • Field injection • Direct bytecode builder API
  • 87. • Home Page http://howardlewisship.com • Tapestry Central Blog http://tapestryjava.blogspot.com/ • Examples Source https://github.com/hlship/plastic-demos • Apache Tapestry http://tapestry.apache.org • ASM http://asm.ow2.org/
  • 88. Q&A
  • 89. Image Credits © 2008 Andrei Niemimäki http://www.flickr.com/photos/andrein/2502654648 © 2008 Don Solo http://www.flickr.com/photos/donsolo/2234406328/ © 2006 Alexandre Duret-Lutz http://www.flickr.com/photos/donsolo/2234406328/ © 2010 Trey Ratcliff http://www.flickr.com/photos/stuckincustoms/4820290530 © 2007 Sandra Gonzalez http://www.flickr.com/photos/la-ultima-en-saber/1209594912/ © 2009 Darwin Bell http://www.flickr.com/photos/53611153@N00/3645850983/ © 2008 *Katch* http://www.flickr.com/photos/13533187@N00/2371264501 © 2010 Christian Guthier http://www.flickr.com/photos/60364452@N00/4445705276/

Editor's Notes