Kerler.Liu
Kerler.Liu

Reputation: 51

Bytebuddy--how to make a new method to invoke the original code of another (rebased) method?

Say originally there is a class AAA written by others:

public final class AAA {
  public final Object xxx(...) {return yyy(...);}
  public final Object yyy(...) {...}
}

I wang to remove 'final' from AAA and AAA.yyy(...) so that I can write a sub class of AAA to override yyy(...).

It seems that directly remove modifier 'final' using ByteBuddy can not make yyy(...) really overridable, i.e. anObjectOfSubclassOfAAA.xxx(...) still invoke the AAA.yyy(...) instead of the yyy(...) in sub class of AAA. (Maybe changing the bytecode (such as invoke/invokeExact/invokeDynamic) in xxx(...){...} for invoking yyy(...) can make yyy(...) really overridable.)

So, maybe one solution is to rebase AAA.yyy(...) to invoke a new non-final method AAA.doYyy(...), and the AAA.doYyy(...) invoke the original method body of AAA.yyy(...), as code below, then the sub class of AAA can override doYyy(...).

//class AAA after rebased.
public class AAA {
  public final Object xxx(...) {return yyy(...);}
  public final Object yyy(...) {return doYyy(...);}
  public Object doYyy(...) {return yyy$origin(...);}
}

public class BBB extends AAA {
  @Override
  public Object doYyy(...) {doSth...}
}

Then, the question is: How to make a new method doYyy(...) to invoke yyy$origin(...) which is the original code of another method yyy(...).

SuperMethodCall does not help here.

Upvotes: 1

Views: 568

Answers (2)

Kerler.Liu
Kerler.Liu

Reputation: 51

Finally I use code below. The critical part is 'parameterDefinitionForNewMethod.intercept(MethodCall.invoke(originalMethod).onSuper().withAllArguments())'. Please search 'parameterDefinition' and 'intercept(MethodCall.invoke(originalMethod).onSuper().withAllArguments())' in code below.

Here 'parameterDefinitionForNewMethod' is the instance of class 'DynamicType.Builder.MethodDefinition.ParameterDefinition<?>'. This instance means the new method 'doGetFieldPredicates(...)' in code below, which is corresponding to the doYyy(...) in the question above.

Here 'originalMethod' means the method 'getFieldPredicates(...)' in code below, which is corresponding to the yyy(...) in the question above.

   builder = makeMethodToDelegateTo_ANewAbstractMethodWithOriginalMethodBodyAsDefaultMethodBody(
                    builder
                    , getDeclaredMethod(classGraphQLJpaQueryFactory, "getFieldPredicates")
                    , "field"
                    , "query"
                    , "cb"
                    , "root"
                    , "from"
                    , "environment"
            );

    protected DynamicType.Builder<?> makeMethodToDelegateTo_ANewAbstractMethodWithOriginalMethodBodyAsDefaultMethodBody(DynamicType.Builder<?> builder, Method originalMethod, String... expectedParameterNames) {
        return makeMethodDelegateToANewMadeMethodWithMethodBody(
                builder
                , originalMethod
                , it -> it.intercept(MethodCall.invoke(originalMethod).onSuper().withAllArguments())
                , expectedParameterNames);
    }

    protected DynamicType.Builder<?> makeMethodDelegateToANewMadeMethodWithMethodBody(
            DynamicType.Builder<?> builder
            , Method originalMethod
            , Function<DynamicType.Builder.MethodDefinition.ParameterDefinition<?>, DynamicType.Builder<?>> functionToMakeMethodBody
            , String... expectedParameterNames) {

        final String nameOfDelegatedMethod = appendCapitalizedString(String__do, originalMethod.getName());

        DynamicType.Builder.MethodDefinition.ParameterDefinition<?> parameterDefinition
                = getParameterDefinitionForMethod(builder, originalMethod, nameOfDelegatedMethod, expectedParameterNames);

        builder = functionToMakeMethodBody.apply(parameterDefinition);

        builder = makeMethodDelegateToAnotherMethod(builder, originalMethod, nameOfDelegatedMethod);

        return builder;
    }

    protected DynamicType.Builder<?> makeMethodDelegateToAnotherMethod(DynamicType.Builder<?> builder, Method originalMethod, String nameOfNewDelegatedMethod) {
        return builder.method(ElementMatchers.is(originalMethod))
                .intercept(MethodCall.invoke(ElementMatchers.named(nameOfNewDelegatedMethod)).withAllArguments())
                .transform(Transformer.ForMethod.withModifiers(Visibility.PROTECTED, MethodManifestation.FINAL));
    }

    protected DynamicType.Builder.MethodDefinition.ParameterDefinition<?> getParameterDefinitionForMethod(DynamicType.Builder<?> builder, Method methodForSignatureReferredTo, String nameOfNewDelegatedMethod, String[] expectedParameterNames) {
        final Parameter[] methodParameters = methodForSignatureReferredTo.getParameters();
        if (expectedParameterNames.length != 0 && expectedParameterNames.length != methodParameters.length) {
            throw new IllegalArgumentException("expectedParameterNames's length is '" + expectedParameterNames.length
                    + "', not zero and not equals to Method '" + methodForSignatureReferredTo.getName() + "' 's parameter length '" + methodParameters.length + "'.");
        }

        DynamicType.Builder.MethodDefinition.ParameterDefinition<?> parameterDefinition = builder.defineMethod(nameOfNewDelegatedMethod, methodForSignatureReferredTo.getGenericReturnType(), Visibility.PROTECTED);

        for (int i = 0; i < methodParameters.length; i++) {
            String parameterName = expectedParameterNames.length != 0 ? expectedParameterNames[i] : methodParameters[i].getName();
            parameterDefinition = parameterDefinition.withParameter(methodParameters[i].getParameterizedType(), parameterName);
        }

        return parameterDefinition;
    }

After the class is rebased, the method bodies are like code below, where 'doGetFieldPredicates(...)' is corresonding to the 'doYyy(...)' in question above. The 'doGetFieldPredicates(...)' invoke the original body of 'getFieldPredicates(...)' and can be overridden by sub class.

public final List<Predicate> getFieldPredicates(Field var1, CriteriaQuery<?> var2, CriteriaBuilder var3, Root<?> var4, From<?, ?> var5, DataFetchingEnvironment var6) {
    return this.doGetFieldPredicates(var1, var2, var3, var4, var5, var6);
}

protected List<Predicate> doGetFieldPredicates(Field field, CriteriaQuery<?> query, CriteriaBuilder cb, Root<?> root, From<?, ?> from, DataFetchingEnvironment environment) {
    return this.getFieldPredicates$original$ntjCvAEf(var1, var2, var3, var4, var5, var6);
}

Upvotes: 1

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44077

The idea of rebasing is to replace the existing method with something else while retaining the ability to invoke the original code. How this is done is an implementation detail of Byte Buddy, and it is not exposed. You can however define a MethodNameTransformer which follows a pattern that is known to you and use a method handle to invoke it.

Upvotes: 0

Related Questions