• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

raphw / byte-buddy / #801

27 Oct 2025 09:37AM UTC coverage: 84.715% (-0.4%) from 85.118%
#801

push

raphw
Fix imports.

29586 of 34924 relevant lines covered (84.72%)

0.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.44
/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Pipe.java
1
/*
2
 * Copyright 2014 - Present Rafael Winterhalter
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package net.bytebuddy.implementation.bind.annotation;
17

18
import net.bytebuddy.ByteBuddy;
19
import net.bytebuddy.ClassFileVersion;
20
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
21
import net.bytebuddy.description.annotation.AnnotationDescription;
22
import net.bytebuddy.description.field.FieldDescription;
23
import net.bytebuddy.description.field.FieldList;
24
import net.bytebuddy.description.method.MethodDescription;
25
import net.bytebuddy.description.method.MethodList;
26
import net.bytebuddy.description.method.ParameterDescription;
27
import net.bytebuddy.description.modifier.Visibility;
28
import net.bytebuddy.description.type.TypeDescription;
29
import net.bytebuddy.description.type.TypeList;
30
import net.bytebuddy.dynamic.DynamicType;
31
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
32
import net.bytebuddy.dynamic.scaffold.TypeValidation;
33
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
34
import net.bytebuddy.implementation.Implementation;
35
import net.bytebuddy.implementation.MethodAccessorFactory;
36
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
37
import net.bytebuddy.implementation.bind.MethodDelegationBinder;
38
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
39
import net.bytebuddy.implementation.bytecode.Duplication;
40
import net.bytebuddy.implementation.bytecode.StackManipulation;
41
import net.bytebuddy.implementation.bytecode.TypeCreation;
42
import net.bytebuddy.implementation.bytecode.assign.Assigner;
43
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
44
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
45
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
46
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
47
import net.bytebuddy.matcher.ElementMatchers;
48
import net.bytebuddy.utility.RandomString;
49
import org.objectweb.asm.MethodVisitor;
50

51
import java.io.Serializable;
52
import java.lang.annotation.Documented;
53
import java.lang.annotation.ElementType;
54
import java.lang.annotation.Retention;
55
import java.lang.annotation.RetentionPolicy;
56
import java.lang.annotation.Target;
57
import java.util.LinkedHashMap;
58
import java.util.Map;
59

60
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
61
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
62
import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
63
import static net.bytebuddy.matcher.ElementMatchers.named;
64

65
/**
66
 * <p>
67
 * A target method parameter that is annotated with this annotation allows to forward an intercepted method
68
 * invocation to another instance. The instance to which a method call is forwarded must be of the most specific
69
 * type that declares the intercepted method on the intercepted type.
70
 * </p>
71
 * <p>
72
 * Unfortunately, before Java 8, the Java Class Library does not define any interface type which takes a single
73
 * {@link java.lang.Object} type and returns another {@link java.lang.Object} type. For this reason, a
74
 * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder} needs to be installed explicitly
75
 * and registered on a {@link net.bytebuddy.implementation.MethodDelegation}. The installed type is allowed to be an
76
 * interface without any super types that declares a single method which maps an {@link java.lang.Object} type to
77
 * a another {@link java.lang.Object} type as a result value. It is however not prohibited to use generics in the
78
 * process.
79
 * </p>
80
 *
81
 * @see net.bytebuddy.implementation.MethodDelegation
82
 * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
83
 */
84
@Documented
85
@Retention(RetentionPolicy.RUNTIME)
86
@Target(ElementType.PARAMETER)
87
public @interface Pipe {
88

89
    /**
90
     * Determines if the generated proxy should be {@link java.io.Serializable}.
91
     *
92
     * @return {@code true} if the generated proxy should be {@link java.io.Serializable}.
93
     */
94
    boolean serializableProxy() default false;
95

96
    /**
97
     * A {@link net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder}
98
     * for binding the {@link net.bytebuddy.implementation.bind.annotation.Pipe} annotation.
99
     */
100
    @HashCodeAndEqualsPlugin.Enhance
101
    class Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> {
102

103
        /**
104
         * A description of the {@link Pipe#serializableProxy()} method.
105
         */
106
        private static final MethodDescription.InDefinedShape SERIALIZABLE_PROXY = TypeDescription.ForLoadedType.of(Pipe.class)
1✔
107
                .getDeclaredMethods()
1✔
108
                .filter(named("serializableProxy"))
1✔
109
                .getOnly();
1✔
110

111
        /**
112
         * The method which implements the behavior of forwarding a method invocation. This method needs to define
113
         * a single non-static method with an {@link java.lang.Object} to {@link java.lang.Object} mapping.
114
         */
115
        private final MethodDescription forwardingMethod;
116

117
        /**
118
         * Creates a new binder. This constructor is not doing any validation of the forwarding method and its
119
         * declaring type. Such validation is normally performed by the
120
         * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder#install(Class)}
121
         * method.
122
         *
123
         * @param forwardingMethod The method which implements the behavior of forwarding a method invocation. This
124
         *                         method needs to define a single non-static method with an {@link java.lang.Object}
125
         *                         to {@link java.lang.Object} mapping.
126
         */
127
        protected Binder(MethodDescription forwardingMethod) {
1✔
128
            this.forwardingMethod = forwardingMethod;
1✔
129
        }
1✔
130

131
        /**
132
         * Installs a given type for use on a {@link net.bytebuddy.implementation.bind.annotation.Pipe}
133
         * annotation. The given type must be an interface without any super interfaces and a single method which
134
         * maps an {@link java.lang.Object} type to another {@link java.lang.Object} type. The use of generics is
135
         * permitted.
136
         *
137
         * @param type The type to install.
138
         * @return A binder for the {@link net.bytebuddy.implementation.bind.annotation.Pipe}
139
         * annotation.
140
         */
141
        public static TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> install(Class<?> type) {
142
            return install(TypeDescription.ForLoadedType.of(type));
1✔
143
        }
144

145
        /**
146
         * Installs a given type for use on a {@link net.bytebuddy.implementation.bind.annotation.Pipe}
147
         * annotation. The given type must be an interface without any super interfaces and a single method which
148
         * maps an {@link java.lang.Object} type to another {@link java.lang.Object} type. The use of generics is
149
         * permitted.
150
         *
151
         * @param typeDescription The type to install.
152
         * @return A binder for the {@link net.bytebuddy.implementation.bind.annotation.Pipe}
153
         * annotation.
154
         */
155
        public static TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> install(TypeDescription typeDescription) {
156
            return new Binder(onlyMethod(typeDescription));
1✔
157
        }
158

159
        /**
160
         * Locates the only method of a type that is compatible to being overridden for invoking the proxy.
161
         *
162
         * @param typeDescription The type that is being installed.
163
         * @return Its only method after validation.
164
         */
165
        private static MethodDescription onlyMethod(TypeDescription typeDescription) {
166
            if (!typeDescription.isInterface()) {
1✔
167
                throw new IllegalArgumentException(typeDescription + " is not an interface");
1✔
168
            } else if (!typeDescription.getInterfaces().isEmpty()) {
1✔
169
                throw new IllegalArgumentException(typeDescription + " must not extend other interfaces");
1✔
170
            } else if (!typeDescription.isPublic()) {
1✔
171
                throw new IllegalArgumentException(typeDescription + " is mot public");
1✔
172
            }
173
            MethodList<?> methodCandidates = typeDescription.getDeclaredMethods().filter(isAbstract());
1✔
174
            if (methodCandidates.size() != 1) {
1✔
175
                throw new IllegalArgumentException(typeDescription + " must declare exactly one abstract method");
1✔
176
            }
177
            MethodDescription methodDescription = methodCandidates.getOnly();
1✔
178
            if (!methodDescription.getReturnType().asErasure().represents(Object.class)) {
1✔
179
                throw new IllegalArgumentException(methodDescription + " does not return an Object-type");
×
180
            } else if (methodDescription.getParameters().size() != 1 || !methodDescription.getParameters().getOnly().getType().asErasure().represents(Object.class)) {
1✔
181
                throw new IllegalArgumentException(methodDescription + " does not take a single Object-typed argument");
1✔
182
            }
183
            return methodDescription;
1✔
184
        }
185

186
        /**
187
         * {@inheritDoc}
188
         */
189
        public Class<Pipe> getHandledType() {
190
            return Pipe.class;
1✔
191
        }
192

193
        /**
194
         * {@inheritDoc}
195
         */
196
        public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<Pipe> annotation,
197
                                                               MethodDescription source,
198
                                                               ParameterDescription target,
199
                                                               Implementation.Target implementationTarget,
200
                                                               Assigner assigner,
201
                                                               Assigner.Typing typing) {
202
            if (!target.getType().asErasure().equals(forwardingMethod.getDeclaringType())) {
1✔
203
                throw new IllegalStateException("Illegal use of @Pipe for " + target + " which was installed for " + forwardingMethod.getDeclaringType());
1✔
204
            } else if (source.isStatic()) {
1✔
205
                return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
1✔
206
            }
207
            return new MethodDelegationBinder.ParameterBinding.Anonymous(new RedirectionProxy(forwardingMethod.getDeclaringType().asErasure(),
1✔
208
                    source,
209
                    assigner,
210
                    annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class)));
1✔
211
        }
212

213
        /**
214
         * An auxiliary type for performing the redirection of a method invocation as requested by the
215
         * {@link net.bytebuddy.implementation.bind.annotation.Pipe} annotation.
216
         */
217
        @HashCodeAndEqualsPlugin.Enhance
218
        protected static class RedirectionProxy extends StackManipulation.AbstractBase implements AuxiliaryType {
219

220
            /**
221
             * The prefix for naming fields to store method arguments.
222
             */
223
            private static final String FIELD_NAME_PREFIX = "argument";
224

225
            /**
226
             * The type that declares the method for forwarding a method invocation.
227
             */
228
            private final TypeDescription forwardingType;
229

230
            /**
231
             * The method that is to be forwarded.
232
             */
233
            private final MethodDescription sourceMethod;
234

235
            /**
236
             * The assigner to use.
237
             */
238
            private final Assigner assigner;
239

240
            /**
241
             * Determines if the generated proxy should be {@link java.io.Serializable}.
242
             */
243
            private final boolean serializableProxy;
244

245
            /**
246
             * Creates a new redirection.
247
             *
248
             * @param forwardingType    The type that declares the method for forwarding a method invocation.
249
             * @param sourceMethod      The method that is to be forwarded.
250
             * @param assigner          The assigner to use.
251
             * @param serializableProxy Determines if the generated proxy should be {@link java.io.Serializable}.
252
             */
253
            protected RedirectionProxy(TypeDescription forwardingType,
254
                                       MethodDescription sourceMethod,
255
                                       Assigner assigner,
256
                                       boolean serializableProxy) {
1✔
257
                this.forwardingType = forwardingType;
1✔
258
                this.sourceMethod = sourceMethod;
1✔
259
                this.assigner = assigner;
1✔
260
                this.serializableProxy = serializableProxy;
1✔
261
            }
1✔
262

263
            /**
264
             * Extracts all parameters of a method to fields.
265
             *
266
             * @param methodDescription The method to extract the parameters from.
267
             * @return A linked hash map of field names to the types of these fields representing all parameters of the
268
             * given method.
269
             */
270
            private static LinkedHashMap<String, TypeDescription> extractFields(MethodDescription methodDescription) {
271
                TypeList parameterTypes = methodDescription.getParameters().asTypeList().asErasures();
1✔
272
                LinkedHashMap<String, TypeDescription> typeDescriptions = new LinkedHashMap<String, TypeDescription>();
1✔
273
                int currentIndex = 0;
1✔
274
                for (TypeDescription parameterType : parameterTypes) {
1✔
275
                    typeDescriptions.put(fieldName(currentIndex++), parameterType);
1✔
276
                }
1✔
277
                return typeDescriptions;
1✔
278
            }
279

280
            /**
281
             * Creates a new field name.
282
             *
283
             * @param index The index of the field.
284
             * @return The field name that corresponds to the index.
285
             */
286
            private static String fieldName(int index) {
287
                return FIELD_NAME_PREFIX + index;
1✔
288
            }
289

290
            /**
291
             * {@inheritDoc}
292
             */
293
            public String getSuffix() {
294
                return RandomString.hashOf(forwardingType.hashCode())
×
295
                        + RandomString.hashOf(sourceMethod.hashCode())
×
296
                        + (serializableProxy ? "S" : "0");
297
            }
298

299
            /**
300
             * {@inheritDoc}
301
             */
302
            public DynamicType make(String auxiliaryTypeName,
303
                                    ClassFileVersion classFileVersion,
304
                                    MethodAccessorFactory methodAccessorFactory) {
305
                LinkedHashMap<String, TypeDescription> parameterFields = extractFields(sourceMethod);
1✔
306
                DynamicType.Builder<?> builder = new ByteBuddy(classFileVersion)
1✔
307
                        .with(TypeValidation.DISABLED)
1✔
308
                        .subclass(forwardingType, ConstructorStrategy.Default.NO_CONSTRUCTORS)
1✔
309
                        .name(auxiliaryTypeName)
1✔
310
                        .modifiers(DEFAULT_TYPE_MODIFIER)
1✔
311
                        .implement(serializableProxy ? new Class<?>[]{Serializable.class} : new Class<?>[0])
1✔
312
                        .method(ElementMatchers.<MethodDescription>isAbstract().and(isDeclaredBy(forwardingType)))
1✔
313
                        .intercept(new MethodCall(sourceMethod, assigner))
1✔
314
                        .defineConstructor().withParameters(parameterFields.values())
1✔
315
                        .intercept(ConstructorCall.INSTANCE);
1✔
316
                for (Map.Entry<String, TypeDescription> field : parameterFields.entrySet()) {
1✔
317
                    builder = builder.defineField(field.getKey(), field.getValue(), Visibility.PRIVATE);
1✔
318
                }
1✔
319
                return builder.make();
1✔
320
            }
321

322
            /**
323
             * {@inheritDoc}
324
             */
325
            public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
326
                TypeDescription forwardingType = implementationContext.register(this);
1✔
327
                return new Compound(
1✔
328
                        TypeCreation.of(forwardingType),
1✔
329
                        Duplication.SINGLE,
330
                        MethodVariableAccess.allArgumentsOf(sourceMethod),
1✔
331
                        MethodInvocation.invoke(forwardingType.getDeclaredMethods().filter(isConstructor()).getOnly())
1✔
332
                ).apply(methodVisitor, implementationContext);
1✔
333
            }
334

335
            /**
336
             * The implementation to implement a {@link RedirectionProxy}'s constructor.
337
             */
338
            protected enum ConstructorCall implements Implementation {
1✔
339

340
                /**
341
                 * The singleton instance.
342
                 */
343
                INSTANCE;
1✔
344

345
                /**
346
                 * A reference of the {@link Object} type default constructor.
347
                 */
348
                private final transient MethodDescription.InDefinedShape objectTypeDefaultConstructor;
349

350
                /**
351
                 * Creates the constructor call singleton.
352
                 */
353
                ConstructorCall() {
1✔
354
                    objectTypeDefaultConstructor = TypeDescription.ForLoadedType.of(Object.class).getDeclaredMethods().filter(isConstructor()).getOnly();
1✔
355
                }
1✔
356

357
                /**
358
                 * {@inheritDoc}
359
                 */
360
                public InstrumentedType prepare(InstrumentedType instrumentedType) {
361
                    return instrumentedType;
1✔
362
                }
363

364
                /**
365
                 * {@inheritDoc}
366
                 */
367
                public ByteCodeAppender appender(Target implementationTarget) {
368
                    return new Appender(implementationTarget.getInstrumentedType());
1✔
369
                }
370

371
                /**
372
                 * The appender for implementing the {@link RedirectionProxy.ConstructorCall}.
373
                 */
374
                @HashCodeAndEqualsPlugin.Enhance
375
                private static class Appender implements ByteCodeAppender {
376

377
                    /**
378
                     * The instrumented type being created.
379
                     */
380
                    private final TypeDescription instrumentedType;
381

382
                    /**
383
                     * Creates a new appender.
384
                     *
385
                     * @param instrumentedType The instrumented type that is being created.
386
                     */
387
                    private Appender(TypeDescription instrumentedType) {
1✔
388
                        this.instrumentedType = instrumentedType;
1✔
389
                    }
1✔
390

391
                    /**
392
                     * {@inheritDoc}
393
                     */
394
                    public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
395
                        FieldList<?> fieldList = instrumentedType.getDeclaredFields();
1✔
396
                        StackManipulation[] fieldLoading = new StackManipulation[fieldList.size()];
1✔
397
                        int index = 0;
1✔
398
                        for (FieldDescription fieldDescription : fieldList) {
1✔
399
                            fieldLoading[index] = new StackManipulation.Compound(
1✔
400
                                    MethodVariableAccess.loadThis(),
1✔
401
                                    MethodVariableAccess.load(instrumentedMethod.getParameters().get(index)),
1✔
402
                                    FieldAccess.forField(fieldDescription).write()
1✔
403
                            );
404
                            index++;
1✔
405
                        }
1✔
406
                        StackManipulation.Size stackSize = new StackManipulation.Compound(
1✔
407
                                MethodVariableAccess.loadThis(),
1✔
408
                                MethodInvocation.invoke(ConstructorCall.INSTANCE.objectTypeDefaultConstructor),
1✔
409
                                new StackManipulation.Compound(fieldLoading),
410
                                MethodReturn.VOID
411
                        ).apply(methodVisitor, implementationContext);
1✔
412
                        return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
1✔
413
                    }
414
                }
415
            }
416

417
            /**
418
             * The implementation to implement a {@link RedirectionProxy}'s forwarding method.
419
             */
420
            @HashCodeAndEqualsPlugin.Enhance
421
            protected static class MethodCall implements Implementation {
422

423
                /**
424
                 * The method that is invoked by the implemented method.
425
                 */
426
                private final MethodDescription redirectedMethod;
427

428
                /**
429
                 * The assigner to be used for invoking the forwarded method.
430
                 */
431
                private final Assigner assigner;
432

433
                /**
434
                 * Creates a new method call implementation.
435
                 *
436
                 * @param redirectedMethod The method that is invoked by the implemented method.
437
                 * @param assigner         The assigner to be used for invoking the forwarded method.
438
                 */
439
                private MethodCall(MethodDescription redirectedMethod, Assigner assigner) {
1✔
440
                    this.redirectedMethod = redirectedMethod;
1✔
441
                    this.assigner = assigner;
1✔
442
                }
1✔
443

444
                /**
445
                 * {@inheritDoc}
446
                 */
447
                public InstrumentedType prepare(InstrumentedType instrumentedType) {
448
                    return instrumentedType;
1✔
449
                }
450

451
                /**
452
                 * {@inheritDoc}
453
                 */
454
                public ByteCodeAppender appender(Target implementationTarget) {
455
                    if (!redirectedMethod.isAccessibleTo(implementationTarget.getInstrumentedType())) {
1✔
456
                        throw new IllegalStateException("Cannot invoke " + redirectedMethod + " from outside of class via @Pipe proxy");
1✔
457
                    }
458
                    return new Appender(implementationTarget.getInstrumentedType());
1✔
459
                }
460

461
                /**
462
                 * The appender for implementing the {@link RedirectionProxy.MethodCall}.
463
                 */
464
                @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
465
                private class Appender implements ByteCodeAppender {
466

467
                    /**
468
                     * The instrumented type that is implemented.
469
                     */
470
                    private final TypeDescription instrumentedType;
471

472
                    /**
473
                     * Creates a new appender.
474
                     *
475
                     * @param instrumentedType The instrumented type to be implemented.
476
                     */
477
                    private Appender(TypeDescription instrumentedType) {
1✔
478
                        this.instrumentedType = instrumentedType;
1✔
479
                    }
1✔
480

481
                    /**
482
                     * {@inheritDoc}
483
                     */
484
                    public Size apply(MethodVisitor methodVisitor,
485
                                      Context implementationContext,
486
                                      MethodDescription instrumentedMethod) {
487
                        FieldList<?> fieldList = instrumentedType.getDeclaredFields();
1✔
488
                        StackManipulation[] fieldLoading = new StackManipulation[fieldList.size()];
1✔
489
                        int index = 0;
1✔
490
                        for (FieldDescription fieldDescription : fieldList) {
1✔
491
                            fieldLoading[index++] = new StackManipulation.Compound(MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read());
1✔
492
                        }
1✔
493
                        StackManipulation.Size stackSize = new StackManipulation.Compound(
1✔
494
                                MethodVariableAccess.REFERENCE.loadFrom(1),
1✔
495
                                assigner.assign(TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class), redirectedMethod.getDeclaringType().asGenericType(), Assigner.Typing.DYNAMIC),
1✔
496
                                new StackManipulation.Compound(fieldLoading),
497
                                MethodInvocation.invoke(redirectedMethod),
1✔
498
                                assigner.assign(redirectedMethod.getReturnType(), instrumentedMethod.getReturnType(), Assigner.Typing.DYNAMIC),
1✔
499
                                MethodReturn.REFERENCE
500
                        ).apply(methodVisitor, implementationContext);
1✔
501
                        return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
1✔
502
                    }
503
                }
504
            }
505
        }
506
    }
507
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc