@Override
 public void processField(CtField field) throws Exception {
   if (!field.getName().equals("Id")) {
     CtMethod ctMethod = checkSetMethodNull(field);
     ctMethod.insertAfter("this.modify();");
   }
 }
  private void changeMethod(CtClass ctClass, CtMethod ctMethod) throws CannotCompileException {
    // basically your before-advice...
    ctMethod.insertBefore("System.out.println(\"started method at \" + new java.util.Date());");
    // basically your after-advice...
    ctMethod.insertAfter("System.out.println(\"ended method at \" + new java.util.Date());");

    // basically your around-advice...
    String methodName = ctMethod.getName();
    String proxyName = methodName + "_$proxy";
    CtMethod proxy = CtNewMethod.copy(ctMethod, proxyName, ctClass, null);
    ctMethod.setName(ctMethod.getName() + "_orig");
    proxy.setBody(
        "{ System.out.println(\"hoot!\"); return $proceed($$);}", "this", ctMethod.getName());
    proxy.setName(methodName);
    ctClass.addMethod(proxy);
  }
  private void handleCompositeField(
      CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
      throws NotFoundException, CannotCompileException {
    if (!persistentField.hasAnnotation(Embedded.class)) {
      return;
    }

    // make sure to add the CompositeOwner interface
    managedCtClass.addInterface(classPool.get(CompositeOwner.class.getName()));

    if (enhancementContext.isCompositeClass(managedCtClass)) {
      // if a composite have a embedded field we need to implement the TRACKER_CHANGER_NAME method
      // as well
      MethodWriter.write(
          managedCtClass,
          ""
              + "public void %1$s(String name) {%n"
              + "  if (%2$s != null) { %2$s.callOwner(\".\" + name) ; }%n}",
          EnhancerConstants.TRACKER_CHANGER_NAME,
          EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME);
    }

    // cleanup previous owner
    fieldWriter.insertBefore(
        String.format(
            "" + "if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n",
            persistentField.getName(),
            CompositeTracker.class.getName(),
            EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER));

    // trigger track changes
    fieldWriter.insertAfter(
        String.format(
            "" + "((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this);%n" + "%5$s(\"%1$s\");",
            persistentField.getName(),
            CompositeTracker.class.getName(),
            CompositeOwner.class.getName(),
            EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
            EnhancerConstants.TRACKER_CHANGER_NAME));
  }
 private static void insertAfter(CtMethod m, String newBody) throws Exception {
   removeNativeModifier(m);
   m.insertAfter(newBody);
 }
  @Override
  public byte[] transform(
      ClassLoader loader,
      String className,
      Class<?> classBeingRedefined,
      ProtectionDomain protectionDomain,
      byte[] classfileBuffer)
      throws IllegalClassFormatException {
    // Don't transform system classes. This reason is:
    // - To improve performance
    // - To avoid unexpected behaviors.
    //   For example, if transforms java.lang.invoke.** classes in Java8
    //   even without any ctClass modification, the classes become broken
    //   and Java stream API call fails unexpectedly.
    //   Maybe this is because CtClass instance generated by ClassPool.makeClass method
    //   is cached on global default ClassPool instance.
    if (isJavaSystemClassName(className)) {
      return null;
    }

    // TODO don't need to do anything for java package classes
    ClassPool classPool = ClassPool.getDefault();
    String hookClassName = HookMethodDef.class.getCanonicalName();
    String initializeSrc = hookInitializeSrc();
    boolean transformed = false;
    InputStream stream = null;
    try {
      stream = new ByteArrayInputStream(classfileBuffer);
      CtClass ctClass = null;
      try {
        ctClass = classPool.makeClass(stream, true);
      } catch (RuntimeException e) {
        // makeClass raises RuntimeException when the existing class is frozen.
        // Since frozen classes are maybe system class, just ignore this exception
        return null;
      }

      for (Pair<CtMethod, TestMethod> pair : allSubMethods(srcTree, ctClass)) {
        CtMethod ctSubMethod = pair.getLeft();
        TestMethod subMethod = pair.getRight();
        if (ctSubMethod.isEmpty()) {
          logger.info("skip empty method: " + ctSubMethod.getLongName());
          continue; // cannot hook empty method
        }

        String subClassQualifiedName = subMethod.getTestClass().getQualifiedName();
        String subMethodSimpleName = subMethod.getSimpleName();
        String subMethodArgClassesStr =
            TestMethod.argClassQualifiedNamesToArgClassesStr(
                getArgClassQualifiedNames(ctSubMethod));
        for (int i = 0; i < subMethod.getCodeBody().size(); i++) {
          CodeLine codeLine = subMethod.getCodeBody().get(i);
          if (i + 1 < subMethod.getCodeBody().size()) {
            CodeLine nextCodeLine = subMethod.getCodeBody().get(i + 1);
            assert codeLine.getEndLine() <= nextCodeLine.getStartLine();
            if (codeLine.getEndLine() == nextCodeLine.getStartLine()) {
              // - if multiple statements exist on a line, insert hook only after the last statement
              // - avoid insertion at the middle of the statement.
              //   The problem happens when multi-line statements are like:
              //   method(1);method(
              //           2);
              continue;
            }
          }

          // Hook should be inserted just after the code has finished
          // since the code inserted by the insertAt method is inserted just before the specified
          // line.
          int insertedLine = subMethod.getCodeBody().get(i).getEndLine() + 1;
          int actualInsertedLine = ctSubMethod.insertAt(insertedLine, false, null);
          ctSubMethod.insertAt(
              insertedLine,
              String.format(
                  "%s%s.beforeCodeLineHook(\"%s\",\"%s\",\"%s\",\"%s\",%d, %d);",
                  initializeSrc,
                  hookClassName,
                  subClassQualifiedName,
                  subMethodSimpleName,
                  subMethodSimpleName,
                  subMethodArgClassesStr,
                  codeLine.getStartLine(),
                  actualInsertedLine));
          transformed = true;
        }
      }

      CtClass exceptionType = classPool.get(Throwable.class.getCanonicalName());
      for (Pair<CtMethod, TestMethod> pair : allRootMethods(srcTree, ctClass)) {
        CtMethod ctRootMethod = pair.getLeft();
        TestMethod rootMethod = pair.getRight();
        if (ctRootMethod.isEmpty()) {
          continue; // cannot hook empty method
        }

        String rootClassQualifiedName = rootMethod.getTestClass().getQualifiedName();
        String rootMethodSimpleName = rootMethod.getSimpleName();
        String rootMethodArgClassesStr =
            TestMethod.argClassQualifiedNamesToArgClassesStr(
                getArgClassQualifiedNames(ctRootMethod));
        for (int i = 0; i < rootMethod.getCodeBody().size(); i++) {
          CodeLine codeLine = rootMethod.getCodeBody().get(i);
          if (i + 1 < rootMethod.getCodeBody().size()) {
            CodeLine nextCodeLine = rootMethod.getCodeBody().get(i + 1);
            assert codeLine.getEndLine() <= nextCodeLine.getStartLine();
            if (codeLine.getEndLine() == nextCodeLine.getStartLine()) {
              // - if multiple statements exist on a line, insert hook only after the last statement
              // - avoid insertion at the middle of the statement.
              //   The problem happens when multi-line statements are like:
              //   method(1);method(
              //           2);
              continue;
              // TODO screen capture is not taken correctly for multiple statements in a line
            }
          }

          // Hook should be inserted just after the code has finished
          // since the code inserted by the insertAt method is inserted just before the specified
          // line.
          int insertedLine = rootMethod.getCodeBody().get(i).getEndLine() + 1;
          int actualInsertedLine = ctRootMethod.insertAt(insertedLine, false, null);
          ctRootMethod.insertAt(
              insertedLine,
              String.format(
                  "%s%s.beforeCodeLineHook(\"%s\",\"%s\",\"%s\",\"%s\",%d,%d);",
                  initializeSrc,
                  hookClassName,
                  rootClassQualifiedName,
                  rootMethodSimpleName,
                  rootMethodSimpleName,
                  rootMethodArgClassesStr,
                  codeLine.getStartLine(),
                  actualInsertedLine));
        }

        ctRootMethod.insertBefore(
            String.format(
                "%s%s.beforeMethodHook(\"%s\",\"%s\",\"%s\");",
                initializeSrc,
                hookClassName,
                rootClassQualifiedName,
                rootMethodSimpleName,
                rootMethodSimpleName));
        ctRootMethod.addCatch(
            String.format(
                "{ %s%s.methodErrorHook(\"%s\",\"%s\",$e); throw $e; }",
                initializeSrc, hookClassName, rootClassQualifiedName, rootMethodSimpleName),
            exceptionType);
        ctRootMethod.insertAfter(
            String.format(
                "%s%s.afterMethodHook(\"%s\",\"%s\");",
                initializeSrc, hookClassName, rootClassQualifiedName, rootMethodSimpleName),
            true);
        transformed = true;
      }

      // don't transform not changed ctClass
      // (to improve performance and avoid unexpected error)
      if (transformed) {
        logger.info("transform " + className);
        return ctClass.toBytecode();
      } else {
        return null;
      }
    } catch (CannotCompileException e) {
      // print error since exception in transform method is just ignored
      System.err.println("exception on " + className);
      e.printStackTrace();
      throw new IllegalClassFormatException(e.getLocalizedMessage());
    } catch (Exception e) {
      // print error since exception in transform method is just ignored
      System.err.println("exception on " + className);
      e.printStackTrace();
      throw new RuntimeException(e);
    } finally {
      IOUtils.closeQuietly(stream);
    }
  }
  private void handleBiDirectionalAssociation(
      CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
      throws NotFoundException, CannotCompileException {
    if (!isPossibleBiDirectionalAssociation(persistentField)) {
      return;
    }
    final CtClass targetEntity = getTargetEntityClass(persistentField);
    if (targetEntity == null) {
      log.debugf(
          "Could not find type of bi-directional association for field [%s#%s]",
          managedCtClass.getName(), persistentField.getName());
      return;
    }
    final String mappedBy = getMappedBy(persistentField, targetEntity);
    if (mappedBy.isEmpty()) {
      log.warnf(
          "Could not find bi-directional association for field [%s#%s]",
          managedCtClass.getName(), persistentField.getName());
      return;
    }

    // create a temporary getter and setter on the target entity to be able to compile our code
    final String mappedByGetterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy;
    final String mappedBySetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy;
    MethodWriter.addGetter(targetEntity, mappedBy, mappedByGetterName);
    MethodWriter.addSetter(targetEntity, mappedBy, mappedBySetterName);

    if (persistentField.hasAnnotation(OneToOne.class)) {
      // only unset when $1 != null to avoid recursion
      fieldWriter.insertBefore(
          String.format(
              "if ($0.%s != null && $1 != null) $0.%<s.%s(null);%n",
              persistentField.getName(), mappedBySetterName));
      fieldWriter.insertAfter(
          String.format(
              "if ($1 != null && $1.%s() != $0) $1.%s($0);%n",
              mappedByGetterName, mappedBySetterName));
    }
    if (persistentField.hasAnnotation(OneToMany.class)) {
      // only remove elements not in the new collection or else we would loose those elements
      // don't use iterator to avoid ConcurrentModException
      fieldWriter.insertBefore(
          String.format(
              "if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s(null); } }%n",
              persistentField.getName(), targetEntity.getName(), mappedBySetterName));
      fieldWriter.insertAfter(
          String.format(
              "if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if (target.%s() != $0) target.%s((%s)$0); } }%n",
              targetEntity.getName(),
              mappedByGetterName,
              mappedBySetterName,
              managedCtClass.getName()));
    }
    if (persistentField.hasAnnotation(ManyToOne.class)) {
      fieldWriter.insertBefore(
          String.format(
              "if ($0.%1$s != null && $0.%1$s.%2$s() != null) $0.%1$s.%2$s().remove($0);%n",
              persistentField.getName(), mappedByGetterName));
      // check .contains($0) to avoid double inserts (but preventing duplicates)
      fieldWriter.insertAfter(
          String.format(
              "if ($1 != null) { java.util.Collection c = $1.%s(); if (c != null && !c.contains($0)) c.add($0); }%n",
              mappedByGetterName));
    }
    if (persistentField.hasAnnotation(ManyToMany.class)) {
      fieldWriter.insertBefore(
          String.format(
              "if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s().remove($0); } }%n",
              persistentField.getName(), targetEntity.getName(), mappedByGetterName));
      fieldWriter.insertAfter(
          String.format(
              "if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; java.util.Collection c = target.%s(); if ( c != $0 && c != null) c.add($0); } }%n",
              targetEntity.getName(), mappedByGetterName));
    }
    // implementation note: association management @OneToMany and @ManyToMay works for add()
    // operations but for remove() a snapshot of the collection is needed so we know what
    // associations to break.
    // another approach that could force that behavior would be to return
    // Collections.unmodifiableCollection() ...
  }