Generate bind and unbind methods for references from fields using asm byte code manipulation.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@572281 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/scrplugin/pom.xml b/scrplugin/pom.xml
index dfac7ef..ad773da 100644
--- a/scrplugin/pom.xml
+++ b/scrplugin/pom.xml
@@ -55,6 +55,13 @@
 			<version>1.0</version>
 			<scope>compile</scope>
 		</dependency>
+		
+		<dependency>
+		   <groupId>asm</groupId>
+		   <artifactId>asm-all</artifactId>
+		   <version>3.0</version>
+		</dependency>
+		
 		<dependency>
 		    <groupId>org.apache.commons</groupId>
 		    <artifactId>commons-io</artifactId>
diff --git a/scrplugin/src/main/java/org/apache/felix/scrplugin/SCRDescriptorMojo.java b/scrplugin/src/main/java/org/apache/felix/scrplugin/SCRDescriptorMojo.java
index ac7625d..c351e4f 100644
--- a/scrplugin/src/main/java/org/apache/felix/scrplugin/SCRDescriptorMojo.java
+++ b/scrplugin/src/main/java/org/apache/felix/scrplugin/SCRDescriptorMojo.java
@@ -42,7 +42,6 @@
 import org.apache.felix.scrplugin.tags.JavaField;
 import org.apache.felix.scrplugin.tags.JavaTag;
 import org.apache.felix.scrplugin.tags.ModifiableJavaClassDescription;
-import org.apache.felix.scrplugin.tags.ModifiableJavaClassDescription.Modification;
 import org.apache.felix.scrplugin.xml.ComponentDescriptorIO;
 import org.apache.felix.scrplugin.xml.MetaTypeIO;
 import org.apache.maven.model.Resource;
@@ -58,7 +57,7 @@
  * generates a service descriptor file based on annotations found in the sources.
  *
  * @goal scr
- * @phase generate-resources
+ * @phase process-classes
  * @description Build Service Descriptors from Java Source
  * @requiresDependencyResolution compile
  */
@@ -94,8 +93,18 @@
      */
     private String metaTypeName;
 
+    /**
+     * This flag controls the generation of the bind/unbind methods.
+     * @parameter default-value="true"
+     */
+    private boolean generateAccessors;
+
+    /**
+     * @see org.apache.maven.plugin.AbstractMojo#execute()
+     */
     public void execute() throws MojoExecutionException, MojoFailureException {
         this.getLog().debug("Starting SCRDescriptorMojo....");
+        this.getLog().debug("..generating accessors: " + this.generateAccessors);
 
         boolean hasFailures = false;
 
@@ -517,38 +526,21 @@
             }
             // if this is a field we look for the bind/unbind methods
             // and create them if they are not availabe
-            if ( false ) {
+            if ( this.generateAccessors ) {
                 if ( reference.getField() != null && component.getJavaClassDescription() instanceof ModifiableJavaClassDescription ) {
-                    String changed = null;
+                    boolean createBind = false;
+                    boolean createUnbind = false;
                     // Only create method if no bind name has been specified
                     if ( bindValue == null && ref.findMethod(ref.getBind()) == null ) {
                         // create bind method
-                        final String realMethodName = "bind" + Character.toUpperCase(name.charAt(0))
-                        + name.substring(1);
-                        changed = ((ModifiableJavaClassDescription)component.getJavaClassDescription()).addProtectedMethod(
-                                realMethodName,
-                                type,
-                                "{this." + reference.getField().getName() + "=param;}");
+                        createBind = true;
                     }
                     if ( unbindValue == null && ref.findMethod(ref.getUnbind()) == null ) {
                         // create unbind method
-                        final String realMethodName = "unbind" + Character.toUpperCase(name.charAt(0))
-                        + name.substring(1);
-                        final String c = ((ModifiableJavaClassDescription)component.getJavaClassDescription()).addProtectedMethod(
-                                realMethodName,
-                                type,
-                                "{this." + reference.getField().getName() + "=null;}");
-                        if ( changed == null ) {
-                            changed = c;
-                        } else {
-                            changed = changed + c;
-                        }
+                        createUnbind = true;
                     }
-                    if ( changed != null ) {
-                        Modification mod = new Modification();
-                        mod.lineNumber = 10;
-                        mod.content = changed;
-                        ((ModifiableJavaClassDescription)component.getJavaClassDescription()).writeClassFile(new Modification[] {mod});
+                    if ( createBind || createUnbind ) {
+                        ((ModifiableJavaClassDescription)component.getJavaClassDescription()).addMethods(name, type, createBind, createUnbind);
                     }
                 }
             }
diff --git a/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/JavaClassDescriptorManager.java b/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/JavaClassDescriptorManager.java
index 2d8a748..4be1e6d 100644
--- a/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/JavaClassDescriptorManager.java
+++ b/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/JavaClassDescriptorManager.java
@@ -74,6 +74,9 @@
     /** The component definitions from other bundles hashed by classname. */
     protected final Map componentDescriptions = new HashMap();
 
+    /** The maven project. */
+    protected final MavenProject project;
+
     /**
      * Construct a new manager.
      * @param log
@@ -85,6 +88,7 @@
                                       final MavenProject project)
     throws MojoFailureException, MojoExecutionException {
         this.log = log;
+        this.project = project;
         this.classloader = this.getCompileClassLoader(project);
 
         // get all the class sources through qdox
@@ -177,6 +181,13 @@
     }
 
     /**
+     * Return the project.
+     */
+    public MavenProject getProject() {
+        return this.project;
+    }
+
+    /**
      * Read the service component description.
      * @param artifact
      * @param entry
diff --git a/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/ModifiableJavaClassDescription.java b/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/ModifiableJavaClassDescription.java
index 28986f8..91a39fa 100644
--- a/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/ModifiableJavaClassDescription.java
+++ b/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/ModifiableJavaClassDescription.java
@@ -18,15 +18,11 @@
  */
 package org.apache.felix.scrplugin.tags;
 
+import org.apache.maven.plugin.MojoExecutionException;
+
 
 public interface ModifiableJavaClassDescription {
 
-    String addProtectedMethod(String name, String paramType, String contents);
-
-    void writeClassFile(Modification[] mods);
-
-    public static final class Modification {
-        public int lineNumber;
-        public String content;
-    }
+    void addMethods(String propertyName, String className, boolean createBind, boolean createUnbind)
+    throws MojoExecutionException;
 }
diff --git a/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/qdox/QDoxJavaClassDescription.java b/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/qdox/QDoxJavaClassDescription.java
index bccf47d..33719bc 100644
--- a/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/qdox/QDoxJavaClassDescription.java
+++ b/scrplugin/src/main/java/org/apache/felix/scrplugin/tags/qdox/QDoxJavaClassDescription.java
@@ -18,9 +18,9 @@
  */
 package org.apache.felix.scrplugin.tags.qdox;
 
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.LineNumberReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -32,6 +32,11 @@
 import org.apache.felix.scrplugin.tags.JavaTag;
 import org.apache.felix.scrplugin.tags.ModifiableJavaClassDescription;
 import org.apache.maven.plugin.MojoExecutionException;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
 
 import com.thoughtworks.qdox.model.DocletTag;
 import com.thoughtworks.qdox.model.JavaClass;
@@ -227,47 +232,70 @@
     }
 
     /**
-     * @see org.apache.felix.scrplugin.tags.ModifiableJavaClassDescription#addProtectedMethod(java.lang.String, java.lang.String, java.lang.String)
+     * @see org.apache.felix.scrplugin.tags.ModifiableJavaClassDescription#addMethods(java.lang.String, java.lang.String, boolean, boolean)
      */
-    public String addProtectedMethod(String name, String paramType, String contents) {
-        final JavaParameter param = new JavaParameter(new Type(paramType), "param");
+    public void addMethods(String propertyName,
+                           String className,
+                           boolean createBind,
+                           boolean createUnbind)
+    throws MojoExecutionException {
+        // now do byte code manipulation
+        final String targetDirectory = this.manager.getProject().getBuild().getOutputDirectory();
+        final String fileName = targetDirectory + File.separatorChar +  this.getName().replace('.', File.separatorChar) + ".class";
+        final ClassNode cn = new ClassNode();
+        try {
+            final ClassReader reader = new ClassReader(new FileInputStream(fileName));
+            reader.accept(cn, 0);
+
+            final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+            cn.accept(writer);
+            this.createBind(writer, propertyName, className);
+            this.createUnbind(writer, propertyName, className);
+
+            final FileOutputStream fos = new FileOutputStream(fileName);
+            fos.write(writer.toByteArray());
+            fos.close();
+        } catch (Exception e) {
+            throw new MojoExecutionException("Unable to add methods to " + this.getName(), e);
+        }
+    }
+
+    protected void createBind(ClassWriter cw, String propertyName, String typeName) {
+        final org.objectweb.asm.Type type = org.objectweb.asm.Type.getType("L" + typeName + ";");
+        final String methodName = "bind" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
+        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, methodName, "(" + type.toString() + ")V", null, null);
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), 1);
+        mv.visitFieldInsn(Opcodes.PUTFIELD, this.getName(), propertyName, type.toString());
+        mv.visitInsn(Opcodes.RETURN);
+        mv.visitMaxs(0, 0);
+        // add to qdox
+        final JavaParameter param = new JavaParameter(new Type(typeName), "param");
         final JavaParameter[] params = new JavaParameter[] {param};
         final com.thoughtworks.qdox.model.JavaMethod meth = new com.thoughtworks.qdox.model.JavaMethod();
-        meth.setName(name);
-        meth.setSourceCode(contents);
+        meth.setName(methodName);
         meth.setParameters(params);
         meth.setModifiers(new String[] {"protected"});
         this.javaClass.addMethod(meth);
-        return "protected void " + name + "(" + paramType + " param)" + contents + " ";
-    }
+      }
 
-    /**
-     * @see org.apache.felix.scrplugin.tags.ModifiableJavaClassDescription#writeClassFile(org.apache.felix.scrplugin.tags.ModifiableJavaClassDescription.Modification[])
-     */
-    public void writeClassFile(Modification[] mods) {
-        if ( mods != null && mods.length > 0 ) {
-            try {
-                final LineNumberReader reader = new LineNumberReader(new FileReader(this.source.getFile()));
-                for(int i=0; i<mods.length; i++) {
-                    int lineNumber = mods[i].lineNumber;
-                    while ( reader.getLineNumber() < lineNumber ) {
-                        final String line = reader.readLine();
-                        System.out.println(line);
-                    }
-                    final String line = reader.readLine();
-                    System.out.print(line);
-                    System.out.println(mods[i].content);
-                }
-                String line;
-                while ( (line = reader.readLine()) != null ) {
-                    System.out.println(line);
-                }
-                reader.close();
-            } catch (IOException e) {
-                // TODO Auto-generated catch block
-                e.printStackTrace();
-            }
-        }
-    }
+    protected void createUnbind(ClassWriter cw, String propertyName, String typeName) {
+        final org.objectweb.asm.Type type = org.objectweb.asm.Type.getType("L" + typeName + ";");
+        final String methodName = "unbind" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
+        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, methodName, "(" + type.toString() + ")V", null, null);
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitInsn(Opcodes.ACONST_NULL);
+        mv.visitFieldInsn(Opcodes.PUTFIELD, this.getName(), propertyName, type.toString());
+        mv.visitInsn(Opcodes.RETURN);
+        mv.visitMaxs(0, 0);
+        // add to qdox
+        final JavaParameter param = new JavaParameter(new Type(typeName), "param");
+        final JavaParameter[] params = new JavaParameter[] {param};
+        final com.thoughtworks.qdox.model.JavaMethod meth = new com.thoughtworks.qdox.model.JavaMethod();
+        meth.setName(methodName);
+        meth.setParameters(params);
+        meth.setModifiers(new String[] {"protected"});
+        this.javaClass.addMethod(meth);
+      }
 
 }