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

PDF
camel-scala.pdf
PDF
Ppl for students unit 4 and 5
PPT
PDF
Gdb cheat sheet
KEY
Parte II Objective C
PDF
Cheat Sheet java
PDF
Objective-C Blocks and Grand Central Dispatch
PPTX
Building High Perf Web Apps - IE8 Firestarter
camel-scala.pdf
Ppl for students unit 4 and 5
Gdb cheat sheet
Parte II Objective C
Cheat Sheet java
Objective-C Blocks and Grand Central Dispatch
Building High Perf Web Apps - IE8 Firestarter

What's hot (16)

ODP
Method Handles in Java
PPTX
Java 10, Java 11 and beyond
PPTX
PDF
Java Cheat Sheet
PDF
JVM Mechanics: When Does the JVM JIT & Deoptimize?
PPT
Core java
PPT
Java basic tutorial by sanjeevini india
PPT
Invoke dynamics
PPTX
The Java memory model made easy
PPTX
Java and OpenJDK: disecting the ecosystem
KEY
Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...
PDF
Java Programming Guide Quick Reference
PDF
03 - Qt UI Development
PPTX
Playing with Java Classes and Bytecode
PPSX
Java Tutorial
PPTX
The definitive guide to java agents
Method Handles in Java
Java 10, Java 11 and beyond
Java Cheat Sheet
JVM Mechanics: When Does the JVM JIT & Deoptimize?
Core java
Java basic tutorial by sanjeevini india
Invoke dynamics
The Java memory model made easy
Java and OpenJDK: disecting the ecosystem
Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...
Java Programming Guide Quick Reference
03 - Qt UI Development
Playing with Java Classes and Bytecode
Java Tutorial
The definitive guide to java agents
Ad

Viewers also liked (6)

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

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

PDF
Jvm internals
PPT
Mastering Java ByteCode
PDF
Core2 Document - Java SCORE Overview.pptx.pdf
PPT
Cocoa for Web Developers
KEY
JavaScript Growing Up
PPT
Smoothing Your Java with DSLs
PPTX
Java concurrency
PDF
Node intro
PDF
Daggerate your code - Write your own annotation processor
PDF
Владимир Иванов. Java 8 и JVM: что нового в HotSpot
PPTX
PDF
TypeScript for Java Developers
ODP
The craft of meta programming on JVM
PPTX
Making Java more dynamic: runtime code generation for the JVM
PPTX
PDF
"What's New in HotSpot JVM 8" @ JPoint 2014, Moscow, Russia
PPTX
MiamiJS - The Future of JavaScript
PPTX
Annotation processing
PDF
Exploring lambdas and invokedynamic for embedded systems
PPTX
Objective-c for Java Developers
Jvm internals
Mastering Java ByteCode
Core2 Document - Java SCORE Overview.pptx.pdf
Cocoa for Web Developers
JavaScript Growing Up
Smoothing Your Java with DSLs
Java concurrency
Node intro
Daggerate your code - Write your own annotation processor
Владимир Иванов. Java 8 и JVM: что нового в HotSpot
TypeScript for Java Developers
The craft of meta programming on JVM
Making Java more dynamic: runtime code generation for the JVM
"What's New in HotSpot JVM 8" @ JPoint 2014, Moscow, Russia
MiamiJS - The Future of JavaScript
Annotation processing
Exploring lambdas and invokedynamic for embedded systems
Objective-c for Java Developers

More from Howard Lewis Ship (11)

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

Recently uploaded (20)

PPT
Teaching material agriculture food technology
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Electronic commerce courselecture one. Pdf
PDF
Machine learning based COVID-19 study performance prediction
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PDF
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
Approach and Philosophy of On baking technology
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PPTX
A Presentation on Artificial Intelligence
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
NewMind AI Monthly Chronicles - July 2025
PDF
Network Security Unit 5.pdf for BCA BBA.
Teaching material agriculture food technology
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Electronic commerce courselecture one. Pdf
Machine learning based COVID-19 study performance prediction
MYSQL Presentation for SQL database connectivity
Per capita expenditure prediction using model stacking based on satellite ima...
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
NewMind AI Weekly Chronicles - August'25 Week I
Approach and Philosophy of On baking technology
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Mobile App Security Testing_ A Comprehensive Guide.pdf
A Presentation on Artificial Intelligence
Advanced methodologies resolving dimensionality complications for autism neur...
Understanding_Digital_Forensics_Presentation.pptx
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Review of recent advances in non-invasive hemoglobin estimation
NewMind AI Monthly Chronicles - July 2025
Network Security Unit 5.pdf for BCA BBA.

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