Repository: pinpoint-apm/pinpoint
Issue: Add addSetter API to InstrumentClass
Labels: enhancement

Add addSetter API to InstrumentClass, similar to how getters and accessors are available.
Use case mentioned in #1439 


Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/bootstrap-core/src/main/java/com/navercorp/pinpoint/bootstrap/instrument/InstrumentClass.java b/bootstrap-core/src/main/java/com/navercorp/pinpoint/bootstrap/instrument/InstrumentClass.java
index b230a3040661..9f2671bc48f3 100644
--- a/bootstrap-core/src/main/java/com/navercorp/pinpoint/bootstrap/instrument/InstrumentClass.java
+++ b/bootstrap-core/src/main/java/com/navercorp/pinpoint/bootstrap/instrument/InstrumentClass.java
@@ -69,6 +69,10 @@ public interface InstrumentClass {
 
     void addGetter(String getterTypeName, String fieldName) throws InstrumentException;
 
+    void addSetter(String setterTypeName, String fieldName) throws InstrumentException;
+
+    void addSetter(String setterTypeName, String fieldName, boolean removeFinal) throws InstrumentException;
+
 
     int addInterceptor(String interceptorClassName) throws InstrumentException;
 
diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/JavassistClass.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/JavassistClass.java
index 5ab2d1515596..a4b3846419ad 100644
--- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/JavassistClass.java
+++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/JavassistClass.java
@@ -31,6 +31,7 @@
 import javassist.CtField;
 import javassist.CtMethod;
 import javassist.CtNewMethod;
+import javassist.Modifier;
 import javassist.NotFoundException;
 import javassist.bytecode.MethodInfo;
 
@@ -48,6 +49,7 @@
 import com.navercorp.pinpoint.exception.PinpointException;
 import com.navercorp.pinpoint.profiler.instrument.AccessorAnalyzer.AccessorDetails;
 import com.navercorp.pinpoint.profiler.instrument.GetterAnalyzer.GetterDetails;
+import com.navercorp.pinpoint.profiler.instrument.SetterAnalyzer.SetterDetails;
 import com.navercorp.pinpoint.profiler.instrument.aspect.AspectWeaverClass;
 import com.navercorp.pinpoint.profiler.interceptor.registry.InterceptorRegistryBinder;
 import com.navercorp.pinpoint.profiler.objectfactory.AutoBindingObjectFactory;
@@ -385,10 +387,66 @@ public void addGetter(String getterTypeName, String fieldName) throws Instrument
             CtClass ctInterface = ctClass.getClassPool().get(getterTypeName);
             ctClass.addInterface(ctInterface);
         } catch (Exception e) {
-            throw new InstrumentException("Fail to add getter: " + getterTypeName, e);
+            throw new InstrumentException("Failed to add getter: " + getterTypeName, e);
         }
     }
 
+    @Override
+    public void addSetter(String setterTypeName, String fieldName) throws InstrumentException {
+        this.addSetter(setterTypeName, fieldName, false);
+    }
+
+    @Override
+    public void addSetter(String setterTypeName, String fieldName, boolean removeFinalFlag) throws InstrumentException {
+        try {
+            Class<?> setterType = pluginContext.injectClass(classLoader, setterTypeName);
+
+            SetterDetails setterDetails = new SetterAnalyzer().analyze(setterType);
+
+            CtField field = ctClass.getField(fieldName);
+
+            if (!field.getType().getName().equals(setterDetails.getFieldType().getName())) {
+                throw new IllegalArgumentException("Argument type of the setter is different with the field type. setterMethod: " + setterDetails.getSetter() + ", fieldType: " + field.getType().getName());
+            }
+
+            final int originalModifiers = field.getModifiers();
+            if (Modifier.isStatic(originalModifiers)) {
+                throw new IllegalArgumentException("Cannot add setter to static fields. setterMethod: " + setterDetails.getSetter().getName() + ", fieldName: " + fieldName);
+            }
+
+            boolean finalRemoved = false;
+            if (Modifier.isFinal(originalModifiers)) {
+                if (!removeFinalFlag) {
+                    throw new IllegalArgumentException("Cannot add setter to final field. setterMethod: " + setterDetails.getSetter().getName() + ", fieldName: " + fieldName);
+                } else {
+                    final int modifiersWithFinalRemoved = Modifier.clear(originalModifiers, Modifier.FINAL);
+                    field.setModifiers(modifiersWithFinalRemoved);
+                    finalRemoved = true;
+                }
+            }
+
+            try {
+                CtMethod setterMethod = CtNewMethod.setter(setterDetails.getSetter().getName(), field);
+                if (setterMethod.getDeclaringClass() != ctClass) {
+                    setterMethod = CtNewMethod.copy(setterMethod, ctClass, null);
+                }
+                ctClass.addMethod(setterMethod);
+
+                CtClass ctInterface = ctClass.getClassPool().get(setterTypeName);
+                ctClass.addInterface(ctInterface);
+            }
+            catch (Exception e) {
+                if (finalRemoved) {
+                    field.setModifiers(originalModifiers);
+                }
+                throw e;
+            }
+        } catch (Exception e) {
+            throw new InstrumentException("Failed to add setter: " + setterTypeName, e);
+        }
+
+    }
+
     @Override
     public int addInterceptor(String interceptorClassName) throws InstrumentException {
         Asserts.notNull(interceptorClassName, "interceptorClassName");
@@ -514,7 +572,7 @@ private int addInterceptor0(String interceptorClassName, Object[] constructorArg
 
     private int addInterceptor0(TargetConstructor c, String interceptorClassName, InterceptorScope scope, ExecutionPolicy executionPolicy, Object... constructorArgs) throws InstrumentException {
         final InstrumentMethod constructor = getConstructor(c.value());
-        
+
         if (constructor == null) {
             throw new NotFoundInstrumentException("Cannot find constructor with parameter types: " + Arrays.toString(c.value()));
         }
@@ -567,7 +625,7 @@ public int addInterceptor(MethodFilter filter, String interceptorClassName) thro
         Asserts.notNull(interceptorClassName, "interceptorClassName");
         return addScopedInterceptor0(filter, interceptorClassName, null, null, null);
     }
-    
+
     @Override
     public int addInterceptor(MethodFilter filter, String interceptorClassName, Object[] constructorArgs) throws InstrumentException {
         Asserts.notNull(filter, "filter");
@@ -658,5 +716,5 @@ public List<InstrumentClass> getNestedClasses(ClassFilter filter) {
 
         return list;
     }
-    
+
 }
diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/SetterAnalyzer.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/SetterAnalyzer.java
new file mode 100644
index 000000000000..f45a5a36c417
--- /dev/null
+++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/SetterAnalyzer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.profiler.instrument;
+
+import com.navercorp.pinpoint.common.util.Asserts;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author HyunGil Jeong
+ */
+public class SetterAnalyzer {
+
+    public SetterDetails analyze(Class<?> setterType) {
+        Asserts.notNull(setterType, "setterType");
+
+        if (!setterType.isInterface()) {
+            throw new IllegalArgumentException("setterType " + setterType + "is not an interface");
+        }
+
+        Method[] methods = setterType.getDeclaredMethods();
+
+        if (methods.length != 1) {
+            throw new IllegalArgumentException("Setter interface must have only one method: " + setterType.getName());
+        }
+
+        Method setter = methods[0];
+        Class<?>[] arguments = setter.getParameterTypes();
+
+        if (arguments.length != 1) {
+            throw new IllegalArgumentException("Setter interface method must have exactly 1 argument: " + setterType.getName());
+        }
+
+        Class<?> fieldType = arguments[0];
+
+        Class<?> returnType = setter.getReturnType();
+
+        if (returnType != void.class) {
+            throw new IllegalArgumentException("Setter must have return type void: " + setterType.getName());
+        }
+
+        return new SetterDetails(setter, fieldType);
+    }
+
+    public static final class SetterDetails {
+        private final Method setter;
+        private final Class<?> fieldType;
+
+        public SetterDetails(Method setter, Class<?> fieldType) {
+            this.setter = setter;
+            this.fieldType = fieldType;
+        }
+
+        public Method getSetter() {
+            return setter;
+        }
+
+        public Class<?> getFieldType() {
+            return fieldType;
+        }
+    }
+}


Test patch: