• 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

99.26
/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/EqualsMethod.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;
17

18
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
20
import net.bytebuddy.build.SafeVarargsPlugin;
21
import net.bytebuddy.description.field.FieldDescription;
22
import net.bytebuddy.description.method.MethodDescription;
23
import net.bytebuddy.description.type.TypeDefinition;
24
import net.bytebuddy.description.type.TypeDescription;
25
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
26
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
27
import net.bytebuddy.implementation.bytecode.StackManipulation;
28
import net.bytebuddy.implementation.bytecode.StackSize;
29
import net.bytebuddy.implementation.bytecode.assign.InstanceCheck;
30
import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
31
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
32
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
33
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
34
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
35
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
36
import net.bytebuddy.matcher.ElementMatcher;
37
import org.objectweb.asm.Label;
38
import org.objectweb.asm.MethodVisitor;
39
import org.objectweb.asm.Opcodes;
40

41
import java.util.ArrayList;
42
import java.util.Arrays;
43
import java.util.Collections;
44
import java.util.Comparator;
45
import java.util.List;
46

47
import static net.bytebuddy.matcher.ElementMatchers.isEquals;
48
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
49
import static net.bytebuddy.matcher.ElementMatchers.named;
50
import static net.bytebuddy.matcher.ElementMatchers.none;
51
import static net.bytebuddy.matcher.ElementMatchers.not;
52

53
/**
54
 * An implementation of {@link Object#equals(Object)} that takes a class's declared fields into consideration. Equality is resolved by comparing two
55
 * instances of the same or a compatible class field by field where reference fields must either both be {@code null} or where the field value of
56
 * the instance upon which the method is invoked returns {@code true} upon calling the value's {@code equals} method. For arrays, the corresponding
57
 * utilities of {@link java.util.Arrays} are used.
58
 */
59
@HashCodeAndEqualsPlugin.Enhance
60
public class EqualsMethod implements Implementation {
61

62
    /**
63
     * The {@link Object#equals(Object)} method.
64
     */
65
    private static final MethodDescription.InDefinedShape EQUALS = TypeDescription.ForLoadedType.of(Object.class)
1✔
66
            .getDeclaredMethods()
1✔
67
            .filter(isEquals())
1✔
68
            .getOnly();
1✔
69

70
    /**
71
     * The baseline equality to check.
72
     */
73
    private final SuperClassCheck superClassCheck;
74

75
    /**
76
     * The instance type compatibility check.
77
     */
78
    private final TypeCompatibilityCheck typeCompatibilityCheck;
79

80
    /**
81
     * A matcher to filter fields that should not be used for a equality resolution.
82
     */
83
    private final ElementMatcher.Junction<? super FieldDescription.InDefinedShape> ignored;
84

85
    /**
86
     * A matcher to determine fields of a reference type that cannot be {@code null}.
87
     */
88
    private final ElementMatcher.Junction<? super FieldDescription.InDefinedShape> nonNullable;
89

90
    /**
91
     * A matcher to determine that a field should be considered by its identity.
92
     */
93
    private final ElementMatcher.Junction<? super FieldDescription.InDefinedShape> identity;
94

95
    /**
96
     * The comparator to apply for ordering fields.
97
     */
98
    private final Comparator<? super FieldDescription.InDefinedShape> comparator;
99

100
    /**
101
     * Creates a new equals method implementation.
102
     *
103
     * @param superClassCheck The baseline equality to check.
104
     */
105
    protected EqualsMethod(SuperClassCheck superClassCheck) {
106
        this(superClassCheck, TypeCompatibilityCheck.EXACT, none(), none(), none(), NaturalOrderComparator.INSTANCE);
1✔
107
    }
1✔
108

109
    /**
110
     * Creates a new equals method implementation.
111
     *
112
     * @param superClassCheck        The baseline equality to check.
113
     * @param typeCompatibilityCheck The instance type compatibility check.
114
     * @param ignored                A matcher to filter fields that should not be used for a equality resolution.
115
     * @param nonNullable            A matcher to determine fields of a reference type that cannot be {@code null}.
116
     * @param identity               A matcher to determine that a field should be considered by its identity.
117
     * @param comparator             The comparator to apply for ordering fields.
118
     */
119
    private EqualsMethod(SuperClassCheck superClassCheck,
120
                         TypeCompatibilityCheck typeCompatibilityCheck,
121
                         ElementMatcher.Junction<? super FieldDescription.InDefinedShape> ignored,
122
                         ElementMatcher.Junction<? super FieldDescription.InDefinedShape> nonNullable,
123
                         ElementMatcher.Junction<? super FieldDescription.InDefinedShape> identity,
124
                         Comparator<? super FieldDescription.InDefinedShape> comparator) {
1✔
125
        this.superClassCheck = superClassCheck;
1✔
126
        this.typeCompatibilityCheck = typeCompatibilityCheck;
1✔
127
        this.ignored = ignored;
1✔
128
        this.nonNullable = nonNullable;
1✔
129
        this.identity = identity;
1✔
130
        this.comparator = comparator;
1✔
131
    }
1✔
132

133
    /**
134
     * Creates an equals implementation that invokes the super class's {@link Object#equals(Object)} method first.
135
     *
136
     * @return An equals implementation that invokes the super class's {@link Object#equals(Object)} method first.
137
     */
138
    public static EqualsMethod requiringSuperClassEquality() {
139
        return new EqualsMethod(SuperClassCheck.ENABLED);
1✔
140
    }
141

142
    /**
143
     * Creates an equals method implementation that does not invoke the super class's {@link Object#equals(Object)} method.
144
     *
145
     * @return An equals method implementation that does not invoke the super class's {@link Object#equals(Object)} method.
146
     */
147
    public static EqualsMethod isolated() {
148
        return new EqualsMethod(SuperClassCheck.DISABLED);
1✔
149
    }
150

151
    /**
152
     * Returns a new version of this equals method implementation that ignores the specified fields additionally to any
153
     * previously specified fields.
154
     *
155
     * @param ignored A matcher to specify any fields that should be ignored.
156
     * @return A new version of this equals method implementation that also ignores any fields matched by the provided matcher.
157
     */
158
    public EqualsMethod withIgnoredFields(ElementMatcher<? super FieldDescription.InDefinedShape> ignored) {
159
        return new EqualsMethod(superClassCheck, typeCompatibilityCheck, this.ignored.<FieldDescription.InDefinedShape>or(ignored), nonNullable, identity, comparator);
1✔
160
    }
161

162
    /**
163
     * Returns a new version of this equals method implementation that does not apply a {@code null} value check for the specified fields
164
     * if they have a reference type additionally to any previously specified fields.
165
     *
166
     * @param nonNullable A matcher to specify any fields that should not be guarded against {@code null} values.
167
     * @return A new version of this equals method implementation that also does not apply {@code null} value checks to any fields matched by
168
     * the provided matcher.
169
     */
170
    public EqualsMethod withNonNullableFields(ElementMatcher<? super FieldDescription.InDefinedShape> nonNullable) {
171
        return new EqualsMethod(superClassCheck, typeCompatibilityCheck, ignored, this.nonNullable.<FieldDescription.InDefinedShape>or(nonNullable), identity, comparator);
1✔
172
    }
173

174
    /**
175
     * Returns a new version of this equals method implementation that considers the matched fields by their identity.
176
     *
177
     * @param identity A matcher to determine that a field should be considered by its identity.
178
     * @return A new version of this equals method implementation that also considers the matched fields by their identity.
179
     */
180
    public EqualsMethod withIdentityFields(ElementMatcher<? super FieldDescription.InDefinedShape> identity) {
181
        return new EqualsMethod(superClassCheck, typeCompatibilityCheck, ignored, nonNullable, this.identity.<FieldDescription.InDefinedShape>or(identity), comparator);
1✔
182
    }
183

184
    /**
185
     * Returns a new version of this equals method that compares fields with primitive types prior to fields with non-primitive types.
186
     *
187
     * @return A new version of this equals method that compares primitive-typed fields before fields with non-primitive-typed fields.
188
     */
189
    public EqualsMethod withPrimitiveTypedFieldsFirst() {
190
        return withFieldOrder(TypePropertyComparator.FOR_PRIMITIVE_TYPES);
1✔
191
    }
192

193
    /**
194
     * Returns a new version of this equals method that compares fields with enumeration types prior to fields with non-enumeration types.
195
     *
196
     * @return A new version of this equals method that compares enumeration-typed fields before fields with non-enumeration-typed fields.
197
     */
198
    public EqualsMethod withEnumerationTypedFieldsFirst() {
199
        return withFieldOrder(TypePropertyComparator.FOR_ENUMERATION_TYPES);
1✔
200
    }
201

202
    /**
203
     * Returns a new version of this equals method that compares fields with primitive wrapper types prior to fields with non-primitive wrapper types.
204
     *
205
     * @return A new version of this equals method that compares primitive wrapper-typed fields before fields with non-primitive wrapper-typed fields.
206
     */
207
    public EqualsMethod withPrimitiveWrapperTypedFieldsFirst() {
208
        return withFieldOrder(TypePropertyComparator.FOR_PRIMITIVE_WRAPPER_TYPES);
1✔
209
    }
210

211
    /**
212
     * Returns a new version of this equals method that compares fields with {@link String} types prior to fields with non-{@link String} types.
213
     *
214
     * @return A new version of this equals method that compares {@link String}-typed fields before fields with non-{@link String}-typed fields.
215
     */
216
    public EqualsMethod withStringTypedFieldsFirst() {
217
        return withFieldOrder(TypePropertyComparator.FOR_STRING_TYPES);
1✔
218
    }
219

220
    /**
221
     * Applies the supplied comparator to determine an order for fields for being compared. Fields with the lowest sort order are compared
222
     * first. Any previously defined comparators are applied prior to the supplied comparator.
223
     *
224
     * @param comparator The comparator to apply.
225
     * @return A new version of this equals method that sorts fields in their application order using the supplied comparator.
226
     */
227
    @SuppressWarnings("unchecked") // In absence of @SafeVarargs
228
    public EqualsMethod withFieldOrder(Comparator<? super FieldDescription.InDefinedShape> comparator) {
229
        return new EqualsMethod(superClassCheck, typeCompatibilityCheck, ignored, nonNullable, identity, new CompoundComparator(this.comparator, comparator));
1✔
230
    }
231

232
    /**
233
     * Returns a new version of this equals method implementation that permits subclasses of the instrumented type to be equal to instances
234
     * of the instrumented type instead of requiring an exact match.
235
     *
236
     * @return A new version of this equals method implementation that permits subclasses of the instrumented type to be equal to instances
237
     * of the instrumented type instead of requiring an exact match.
238
     */
239
    public EqualsMethod withSubclassEquality() {
240
        return new EqualsMethod(superClassCheck, TypeCompatibilityCheck.SUBCLASS, ignored, nonNullable, identity, comparator);
1✔
241
    }
242

243
    /**
244
     * {@inheritDoc}
245
     */
246
    public InstrumentedType prepare(InstrumentedType instrumentedType) {
247
        return instrumentedType;
1✔
248
    }
249

250
    /**
251
     * {@inheritDoc}
252
     */
253
    public ByteCodeAppender appender(Target implementationTarget) {
254
        if (implementationTarget.getInstrumentedType().isInterface()) {
1✔
255
            throw new IllegalStateException("Cannot implement meaningful equals method for " + implementationTarget.getInstrumentedType());
1✔
256
        }
257
        List<FieldDescription.InDefinedShape> fields = new ArrayList<FieldDescription.InDefinedShape>(implementationTarget.getInstrumentedType()
1✔
258
                .getDeclaredFields()
1✔
259
                .filter(not(isStatic().or(ignored))));
1✔
260
        Collections.sort(fields, comparator);
1✔
261
        return new Appender(implementationTarget.getInstrumentedType(), new StackManipulation.Compound(
1✔
262
                superClassCheck.resolve(implementationTarget.getInstrumentedType()),
1✔
263
                MethodVariableAccess.loadThis(),
1✔
264
                MethodVariableAccess.REFERENCE.loadFrom(1),
1✔
265
                ConditionalReturn.onIdentity().returningTrue(),
1✔
266
                typeCompatibilityCheck.resolve(implementationTarget.getInstrumentedType())
1✔
267
        ), fields, nonNullable, identity);
268
    }
269

270
    /**
271
     * Checks the equality contract against the super class.
272
     */
273
    protected enum SuperClassCheck {
1✔
274

275
        /**
276
         * Does not perform any super class check.
277
         */
278
        DISABLED {
1✔
279
            @Override
280
            protected StackManipulation resolve(TypeDescription instrumentedType) {
281
                return StackManipulation.Trivial.INSTANCE;
1✔
282
            }
283
        },
284

285
        /**
286
         * Invokes the super class's {@link Object#equals(Object)} method.
287
         */
288
        ENABLED {
1✔
289
            @Override
290
            protected StackManipulation resolve(TypeDescription instrumentedType) {
291
                TypeDefinition superClass = instrumentedType.getSuperClass();
1✔
292
                if (superClass == null) {
1✔
293
                    throw new IllegalStateException(instrumentedType + " does not declare a super class");
×
294
                }
295
                return new StackManipulation.Compound(MethodVariableAccess.loadThis(),
1✔
296
                        MethodVariableAccess.REFERENCE.loadFrom(1),
1✔
297
                        MethodInvocation.invoke(EQUALS).special(superClass.asErasure()),
1✔
298
                        ConditionalReturn.onZeroInteger());
1✔
299
            }
300
        };
301

302
        /**
303
         * Resolves a stack manipulation for the required super class check.
304
         *
305
         * @param instrumentedType The instrumented type.
306
         * @return A stack manipulation that implements the specified check.
307
         */
308
        protected abstract StackManipulation resolve(TypeDescription instrumentedType);
309
    }
310

311
    /**
312
     * Checks the overall type of the provided argument.
313
     */
314
    protected enum TypeCompatibilityCheck {
1✔
315

316
        /**
317
         * Requires an exact type match.
318
         */
319
        EXACT {
1✔
320
            @Override
321
            public StackManipulation resolve(TypeDescription instrumentedType) {
322
                return new StackManipulation.Compound(
1✔
323
                        MethodVariableAccess.REFERENCE.loadFrom(1),
1✔
324
                        ConditionalReturn.onNullValue(),
1✔
325
                        MethodVariableAccess.REFERENCE.loadFrom(0),
1✔
326
                        MethodInvocation.invoke(GET_CLASS),
1✔
327
                        MethodVariableAccess.REFERENCE.loadFrom(1),
1✔
328
                        MethodInvocation.invoke(GET_CLASS),
1✔
329
                        ConditionalReturn.onNonIdentity()
1✔
330
                );
331
            }
332
        },
333

334
        /**
335
         * Requires a subtype relationship.
336
         */
337
        SUBCLASS {
1✔
338
            @Override
339
            protected StackManipulation resolve(TypeDescription instrumentedType) {
340
                return new StackManipulation.Compound(
1✔
341
                        MethodVariableAccess.REFERENCE.loadFrom(1),
1✔
342
                        InstanceCheck.of(instrumentedType),
1✔
343
                        ConditionalReturn.onZeroInteger()
1✔
344
                );
345
            }
346
        };
347

348
        /**
349
         * The {@link Object#getClass()} method.
350
         */
351
        protected static final MethodDescription.InDefinedShape GET_CLASS = TypeDescription.ForLoadedType.of(Object.class)
1✔
352
                .getDeclaredMethods()
1✔
353
                .filter(named("getClass"))
1✔
354
                .getOnly();
1✔
355

356
        /**
357
         * Resolves a stack manipulation for the required type compatibility check.
358
         *
359
         * @param instrumentedType The instrumented type.
360
         * @return A stack manipulation that implements the specified check.
361
         */
362
        protected abstract StackManipulation resolve(TypeDescription instrumentedType);
363
    }
364

365
    /**
366
     * Guards a field value against a potential {@code null} value.
367
     */
368
    protected interface NullValueGuard {
369

370
        /**
371
         * Returns a stack manipulation to apply before computing equality.
372
         *
373
         * @return A stack manipulation to apply before computing equality.
374
         */
375
        StackManipulation before();
376

377
        /**
378
         * Returns a stack manipulation to apply after computing equality.
379
         *
380
         * @return A stack manipulation to apply after computing equality.
381
         */
382
        StackManipulation after();
383

384
        /**
385
         * Returns the required padding for the local variable array to apply this guard.
386
         *
387
         * @return The required padding for the local variable array to apply this guard.
388
         */
389
        int getRequiredVariablePadding();
390

391
        /**
392
         * A non-operational null value guard.
393
         */
394
        enum NoOp implements NullValueGuard {
1✔
395

396
            /**
397
             * The singleton instance.
398
             */
399
            INSTANCE;
1✔
400

401
            /**
402
             * {@inheritDoc}
403
             */
404
            public StackManipulation before() {
405
                return StackManipulation.Trivial.INSTANCE;
1✔
406
            }
407

408
            /**
409
             * {@inheritDoc}
410
             */
411
            public StackManipulation after() {
412
                return StackManipulation.Trivial.INSTANCE;
1✔
413
            }
414

415
            /**
416
             * {@inheritDoc}
417
             */
418
            public int getRequiredVariablePadding() {
419
                return StackSize.ZERO.getSize();
1✔
420
            }
421
        }
422

423
        /**
424
         * A null value guard that expects a reference type and that skips the comparison if both values are {@code null} but returns if
425
         * the invoked instance's field value is {@code null} but not the compared instance's value.
426
         */
427
        @HashCodeAndEqualsPlugin.Enhance
428
        class UsingJump implements NullValueGuard {
429

430
            /**
431
             * The instrumented method.
432
             */
433
            private final MethodDescription instrumentedMethod;
434

435
            /**
436
             * The label to jump to if the first value is {@code null} whereas the second value is not {@code null}.
437
             */
438
            private final Label firstValueNull;
439

440
            /**
441
             * The label to jump to if the second value is {@code null}.
442
             */
443
            private final Label secondValueNull;
444

445
            /**
446
             * A label indicating the end of the null-guarding block.
447
             */
448
            private final Label endOfBlock;
449

450
            /**
451
             * Creates a new null value guard using a jump instruction for {@code null} values.
452
             *
453
             * @param instrumentedMethod The instrumented method.
454
             */
455
            protected UsingJump(MethodDescription instrumentedMethod) {
1✔
456
                this.instrumentedMethod = instrumentedMethod;
1✔
457
                firstValueNull = new Label();
1✔
458
                secondValueNull = new Label();
1✔
459
                endOfBlock = new Label();
1✔
460
            }
1✔
461

462
            /**
463
             * {@inheritDoc}
464
             */
465
            public StackManipulation before() {
466
                return new UsingJump.BeforeInstruction();
1✔
467
            }
468

469
            /**
470
             * {@inheritDoc}
471
             */
472
            public StackManipulation after() {
473
                return new UsingJump.AfterInstruction();
1✔
474
            }
475

476
            /**
477
             * {@inheritDoc}
478
             */
479
            public int getRequiredVariablePadding() {
480
                return 2;
1✔
481
            }
482

483
            /**
484
             * The stack manipulation to apply before the equality computation.
485
             */
486
            @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
487
            protected class BeforeInstruction extends StackManipulation.AbstractBase {
1✔
488

489
                /**
490
                 * {@inheritDoc}
491
                 */
492
                public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
493
                    methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize());
1✔
494
                    methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize() + 1);
1✔
495
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize() + 1);
1✔
496
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize());
1✔
497
                    methodVisitor.visitJumpInsn(Opcodes.IFNULL, secondValueNull);
1✔
498
                    methodVisitor.visitJumpInsn(Opcodes.IFNULL, firstValueNull);
1✔
499
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize() + 1);
1✔
500
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize());
1✔
501
                    return Size.ZERO;
1✔
502
                }
503
            }
504

505
            /**
506
             * The stack manipulation to apply after the equality computation.
507
             */
508
            @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
509
            protected class AfterInstruction extends StackManipulation.AbstractBase {
1✔
510

511
                /**
512
                 * {@inheritDoc}
513
                 */
514
                public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
515
                    methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfBlock);
1✔
516
                    methodVisitor.visitLabel(secondValueNull);
1✔
517
                    implementationContext.getFrameGeneration().same1(methodVisitor,
1✔
518
                            TypeDescription.ForLoadedType.of(Object.class),
1✔
519
                            Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
1✔
520
                    methodVisitor.visitJumpInsn(Opcodes.IFNULL, endOfBlock);
1✔
521
                    methodVisitor.visitLabel(firstValueNull);
1✔
522
                    implementationContext.getFrameGeneration().same(methodVisitor,
1✔
523
                            Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
1✔
524
                    methodVisitor.visitInsn(Opcodes.ICONST_0);
1✔
525
                    methodVisitor.visitInsn(Opcodes.IRETURN);
1✔
526
                    methodVisitor.visitLabel(endOfBlock);
1✔
527
                    implementationContext.getFrameGeneration().same(methodVisitor,
1✔
528
                            Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
1✔
529
                    return Size.ZERO;
1✔
530
                }
531
            }
532
        }
533
    }
534

535
    /**
536
     * A value comparator is responsible to compare to values of a given type.
537
     */
538
    protected enum ValueComparator implements StackManipulation {
1✔
539

540
        /**
541
         * A comparator for a {@code long} value.
542
         */
543
        LONG {
1✔
544
            /** {@inheritDoc} */
545
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
546
                methodVisitor.visitInsn(Opcodes.LCMP);
1✔
547
                return new Size(-2, 0);
1✔
548
            }
549
        },
550

551
        /**
552
         * A comparator for a {@code float} value.
553
         */
554
        FLOAT {
1✔
555
            /** {@inheritDoc} */
556
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
557
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "compare", "(FF)I", false);
1✔
558
                return new Size(-1, 0);
1✔
559
            }
560
        },
561

562
        /**
563
         * A comparator for a {@code double} value.
564
         */
565
        DOUBLE {
1✔
566
            /** {@inheritDoc} */
567
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
568
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "compare", "(DD)I", false);
1✔
569
                return new Size(-2, 0);
1✔
570
            }
571
        },
572

573
        /**
574
         * A comparator for a {@code boolean[]} value.
575
         */
576
        BOOLEAN_ARRAY {
1✔
577
            /** {@inheritDoc} */
578
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
579
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([Z[Z)Z", false);
1✔
580
                return new Size(-1, 0);
1✔
581
            }
582
        },
583

584
        /**
585
         * A comparator for a {@code byte[]} value.
586
         */
587
        BYTE_ARRAY {
1✔
588
            /** {@inheritDoc} */
589
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
590
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([B[B)Z", false);
1✔
591
                return new Size(-1, 0);
1✔
592
            }
593
        },
594

595
        /**
596
         * A comparator for a {@code short[]} value.
597
         */
598
        SHORT_ARRAY {
1✔
599
            /** {@inheritDoc} */
600
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
601
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([S[S)Z", false);
1✔
602
                return new Size(-1, 0);
1✔
603
            }
604
        },
605

606
        /**
607
         * A comparator for a {@code char[]} value.
608
         */
609
        CHARACTER_ARRAY {
1✔
610
            /** {@inheritDoc} */
611
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
612
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([C[C)Z", false);
1✔
613
                return new Size(-1, 0);
1✔
614
            }
615
        },
616

617
        /**
618
         * A comparator for an {@code int[]} value.
619
         */
620
        INTEGER_ARRAY {
1✔
621
            /** {@inheritDoc} */
622
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
623
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([I[I)Z", false);
1✔
624
                return new Size(-1, 0);
1✔
625
            }
626
        },
627

628
        /**
629
         * A comparator for a {@code long[]} value.
630
         */
631
        LONG_ARRAY {
1✔
632
            /** {@inheritDoc} */
633
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
634
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([J[J)Z", false);
1✔
635
                return new Size(-1, 0);
1✔
636
            }
637
        },
638

639
        /**
640
         * A comparator for a {@code float[]} value.
641
         */
642
        FLOAT_ARRAY {
1✔
643
            /** {@inheritDoc} */
644
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
645
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([F[F)Z", false);
1✔
646
                return new Size(-1, 0);
1✔
647
            }
648
        },
649

650
        /**
651
         * A transformer for a {@code double[]} value.
652
         */
653
        DOUBLE_ARRAY {
1✔
654
            /** {@inheritDoc} */
655
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
656
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([D[D)Z", false);
1✔
657
                return new Size(-1, 0);
1✔
658
            }
659
        },
660

661
        /**
662
         * A transformer for a reference array value.
663
         */
664
        REFERENCE_ARRAY {
1✔
665
            /** {@inheritDoc} */
666
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
667
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([Ljava/lang/Object;[Ljava/lang/Object;)Z", false);
1✔
668
                return new Size(-1, 0);
1✔
669
            }
670
        },
671

672
        /**
673
         * A transformer for a nested reference array value.
674
         */
675
        NESTED_ARRAY {
1✔
676
            /** {@inheritDoc} */
677
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
678
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "deepEquals", "([Ljava/lang/Object;[Ljava/lang/Object;)Z", false);
1✔
679
                return new Size(-1, 0);
1✔
680
            }
681
        };
682

683
        /**
684
         * Resolves a type definition to a equality comparison.
685
         *
686
         * @param typeDefinition The type definition to resolve.
687
         * @return The stack manipulation to apply.
688
         */
689
        @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming component type for array type.")
690
        public static StackManipulation of(TypeDefinition typeDefinition) {
691
            if (typeDefinition.represents(boolean.class)
1✔
692
                    || typeDefinition.represents(byte.class)
1✔
693
                    || typeDefinition.represents(short.class)
1✔
694
                    || typeDefinition.represents(char.class)
1✔
695
                    || typeDefinition.represents(int.class)) {
1✔
696
                return ConditionalReturn.onNonEqualInteger();
1✔
697
            } else if (typeDefinition.represents(long.class)) {
1✔
698
                return new Compound(LONG, ConditionalReturn.onNonZeroInteger());
1✔
699
            } else if (typeDefinition.represents(float.class)) {
1✔
700
                return new Compound(FLOAT, ConditionalReturn.onNonZeroInteger());
1✔
701
            } else if (typeDefinition.represents(double.class)) {
1✔
702
                return new Compound(DOUBLE, ConditionalReturn.onNonZeroInteger());
1✔
703
            } else if (typeDefinition.represents(boolean[].class)) {
1✔
704
                return new Compound(BOOLEAN_ARRAY, ConditionalReturn.onZeroInteger());
1✔
705
            } else if (typeDefinition.represents(byte[].class)) {
1✔
706
                return new Compound(BYTE_ARRAY, ConditionalReturn.onZeroInteger());
1✔
707
            } else if (typeDefinition.represents(short[].class)) {
1✔
708
                return new Compound(SHORT_ARRAY, ConditionalReturn.onZeroInteger());
1✔
709
            } else if (typeDefinition.represents(char[].class)) {
1✔
710
                return new Compound(CHARACTER_ARRAY, ConditionalReturn.onZeroInteger());
1✔
711
            } else if (typeDefinition.represents(int[].class)) {
1✔
712
                return new Compound(INTEGER_ARRAY, ConditionalReturn.onZeroInteger());
1✔
713
            } else if (typeDefinition.represents(long[].class)) {
1✔
714
                return new Compound(LONG_ARRAY, ConditionalReturn.onZeroInteger());
1✔
715
            } else if (typeDefinition.represents(float[].class)) {
1✔
716
                return new Compound(FLOAT_ARRAY, ConditionalReturn.onZeroInteger());
1✔
717
            } else if (typeDefinition.represents(double[].class)) {
1✔
718
                return new Compound(DOUBLE_ARRAY, ConditionalReturn.onZeroInteger());
1✔
719
            } else if (typeDefinition.isArray()) {
1✔
720
                return new Compound(typeDefinition.getComponentType().isArray()
1✔
721
                        ? NESTED_ARRAY
722
                        : REFERENCE_ARRAY, ConditionalReturn.onZeroInteger());
1✔
723
            } else {
724
                return new Compound(MethodInvocation.invoke(EQUALS).virtual(typeDefinition.asErasure()), ConditionalReturn.onZeroInteger());
1✔
725
            }
726
        }
727

728
        /**
729
         * {@inheritDoc}
730
         */
731
        public boolean isValid() {
732
            return true;
×
733
        }
734
    }
735

736
    /**
737
     * A byte code appender to implement the {@link EqualsMethod}.
738
     */
739
    @HashCodeAndEqualsPlugin.Enhance
740
    protected static class Appender implements ByteCodeAppender {
741

742
        /**
743
         * The instrumented type.
744
         */
745
        private final TypeDescription instrumentedType;
746

747
        /**
748
         * The baseline stack manipulation.
749
         */
750
        private final StackManipulation baseline;
751

752
        /**
753
         * A list of fields to use for the comparison.
754
         */
755
        private final List<FieldDescription.InDefinedShape> fieldDescriptions;
756

757
        /**
758
         * A matcher to determine fields of a reference type that cannot be {@code null}.
759
         */
760
        private final ElementMatcher<? super FieldDescription.InDefinedShape> nonNullable;
761

762
        /**
763
         * A matcher to determine fields of a reference type that cannot be {@code null}.
764
         */
765
        private final ElementMatcher<? super FieldDescription.InDefinedShape> identity;
766

767
        /**
768
         * Creates a new appender.
769
         *
770
         * @param instrumentedType  The instrumented type.
771
         * @param baseline          The baseline stack manipulation.
772
         * @param fieldDescriptions A list of fields to use for the comparison.
773
         * @param nonNullable       A matcher to determine fields of a reference type that cannot be {@code null}.
774
         * @param identity          A matcher to determine that a field should be considered by its identity.
775
         */
776
        protected Appender(TypeDescription instrumentedType,
777
                           StackManipulation baseline,
778
                           List<FieldDescription.InDefinedShape> fieldDescriptions,
779
                           ElementMatcher<? super FieldDescription.InDefinedShape> nonNullable,
780
                           ElementMatcher<? super FieldDescription.InDefinedShape> identity) {
1✔
781
            this.instrumentedType = instrumentedType;
1✔
782
            this.baseline = baseline;
1✔
783
            this.fieldDescriptions = fieldDescriptions;
1✔
784
            this.nonNullable = nonNullable;
1✔
785
            this.identity = identity;
1✔
786
        }
1✔
787

788
        /**
789
         * {@inheritDoc}
790
         */
791
        public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
792
            if (instrumentedMethod.isStatic()) {
1✔
793
                throw new IllegalStateException("Hash code method must not be static: " + instrumentedMethod);
1✔
794
            } else if (instrumentedMethod.getParameters().size() != 1 || instrumentedMethod.getParameters().getOnly().getType().isPrimitive()) {
1✔
795
                throw new IllegalStateException();
1✔
796
            } else if (!instrumentedMethod.getReturnType().represents(boolean.class)) {
1✔
797
                throw new IllegalStateException("Hash code method does not return primitive boolean: " + instrumentedMethod);
1✔
798
            }
799
            List<StackManipulation> stackManipulations = new ArrayList<StackManipulation>(3 + fieldDescriptions.size() * 8);
1✔
800
            stackManipulations.add(baseline);
1✔
801
            int padding = 0;
1✔
802
            for (FieldDescription.InDefinedShape fieldDescription : fieldDescriptions) {
1✔
803
                stackManipulations.add(MethodVariableAccess.loadThis());
1✔
804
                stackManipulations.add(FieldAccess.forField(fieldDescription).read());
1✔
805
                stackManipulations.add(MethodVariableAccess.REFERENCE.loadFrom(1));
1✔
806
                stackManipulations.add(TypeCasting.to(instrumentedType));
1✔
807
                stackManipulations.add(FieldAccess.forField(fieldDescription).read());
1✔
808
                if (!fieldDescription.getType().isPrimitive() && identity.matches(fieldDescription)) {
1✔
809
                    stackManipulations.add(ConditionalReturn.onNonIdentity());
1✔
810
                } else {
811
                    NullValueGuard nullValueGuard = fieldDescription.getType().isPrimitive() || fieldDescription.getType().isArray() || nonNullable.matches(fieldDescription)
1✔
812
                            ? NullValueGuard.NoOp.INSTANCE
813
                            : new NullValueGuard.UsingJump(instrumentedMethod);
814
                    stackManipulations.add(nullValueGuard.before());
1✔
815
                    stackManipulations.add(ValueComparator.of(fieldDescription.getType()));
1✔
816
                    stackManipulations.add(nullValueGuard.after());
1✔
817
                    padding = Math.max(padding, nullValueGuard.getRequiredVariablePadding());
1✔
818
                }
819
            }
1✔
820
            stackManipulations.add(IntegerConstant.forValue(true));
1✔
821
            stackManipulations.add(MethodReturn.INTEGER);
1✔
822
            return new Size(new StackManipulation.Compound(stackManipulations).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize() + padding);
1✔
823
        }
824
    }
825

826
    /**
827
     * A conditional return aborts the equality computation if a given condition was reached.
828
     */
829
    @HashCodeAndEqualsPlugin.Enhance
830
    protected static class ConditionalReturn extends StackManipulation.AbstractBase {
831

832
        /**
833
         * The conditional jump instruction upon which the return is not triggered.
834
         */
835
        private final int jumpCondition;
836

837
        /**
838
         * The opcode for the value being returned.
839
         */
840
        private final int value;
841

842
        /**
843
         * Creates a conditional return for a value of {@code false}.
844
         *
845
         * @param jumpCondition The opcode upon which the return is not triggered.
846
         */
847
        protected ConditionalReturn(int jumpCondition) {
848
            this(jumpCondition, Opcodes.ICONST_0);
1✔
849
        }
1✔
850

851
        /**
852
         * Creates a conditional return.
853
         *
854
         * @param jumpCondition The opcode upon which the return is not triggered.
855
         * @param value         The opcode for the value being returned.
856
         */
857
        private ConditionalReturn(int jumpCondition, int value) {
1✔
858
            this.jumpCondition = jumpCondition;
1✔
859
            this.value = value;
1✔
860
        }
1✔
861

862
        /**
863
         * Returns a conditional return that returns on an {@code int} value of {@code 0}.
864
         *
865
         * @return A conditional return that returns on an {@code int} value of {@code 0}.
866
         */
867
        protected static ConditionalReturn onZeroInteger() {
868
            return new ConditionalReturn(Opcodes.IFNE);
1✔
869
        }
870

871
        /**
872
         * Returns a conditional return that returns on an {@code int} value of not {@code 0}.
873
         *
874
         * @return A conditional return that returns on an {@code int} value of not {@code 0}.
875
         */
876
        protected static ConditionalReturn onNonZeroInteger() {
877
            return new ConditionalReturn(Opcodes.IFEQ);
1✔
878
        }
879

880
        /**
881
         * Returns a conditional return that returns on a reference value of {@code null}.
882
         *
883
         * @return A conditional return that returns on a reference value of {@code null}.
884
         */
885
        protected static ConditionalReturn onNullValue() {
886
            return new ConditionalReturn(Opcodes.IFNONNULL);
1✔
887
        }
888

889
        /**
890
         * Returns a conditional return that returns if two reference values are not identical.
891
         *
892
         * @return A conditional return that returns if two reference values are not identical.
893
         */
894
        protected static ConditionalReturn onNonIdentity() {
895
            return new ConditionalReturn(Opcodes.IF_ACMPEQ);
1✔
896
        }
897

898
        /**
899
         * Returns a conditional return that returns if two reference values are identical.
900
         *
901
         * @return A conditional return that returns if two reference values are identical.
902
         */
903
        protected static ConditionalReturn onIdentity() {
904
            return new ConditionalReturn(Opcodes.IF_ACMPNE);
1✔
905
        }
906

907
        /**
908
         * Returns a conditional return that returns if two {@code int} values are not equal.
909
         *
910
         * @return A conditional return that returns if two {@code int} values are not equal.
911
         */
912
        protected static ConditionalReturn onNonEqualInteger() {
913
            return new ConditionalReturn(Opcodes.IF_ICMPEQ);
1✔
914
        }
915

916
        /**
917
         * Returns a new stack manipulation that returns {@code true} for the given condition.
918
         *
919
         * @return A new stack manipulation that returns {@code true} for the given condition.
920
         */
921
        protected StackManipulation returningTrue() {
922
            return new ConditionalReturn(jumpCondition, Opcodes.ICONST_1);
1✔
923
        }
924

925
        /**
926
         * {@inheritDoc}
927
         */
928
        public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
929
            Label label = new Label();
1✔
930
            methodVisitor.visitJumpInsn(jumpCondition, label);
1✔
931
            methodVisitor.visitInsn(value);
1✔
932
            methodVisitor.visitInsn(Opcodes.IRETURN);
1✔
933
            methodVisitor.visitLabel(label);
1✔
934
            implementationContext.getFrameGeneration().same(methodVisitor,
1✔
935
                    Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
1✔
936
            return new Size(-1, 1);
1✔
937
        }
938
    }
939

940
    /**
941
     * A comparator that retains the natural order.
942
     */
943
    protected enum NaturalOrderComparator implements Comparator<FieldDescription.InDefinedShape> {
1✔
944

945
        /**
946
         * The singleton instance.
947
         */
948
        INSTANCE;
1✔
949

950
        /**
951
         * {@inheritDoc}
952
         */
953
        public int compare(FieldDescription.InDefinedShape left, FieldDescription.InDefinedShape right) {
954
            return 0;
1✔
955
        }
956
    }
957

958
    /**
959
     * A comparator that sorts fields by a type property.
960
     */
961
    protected enum TypePropertyComparator implements Comparator<FieldDescription.InDefinedShape> {
1✔
962

963
        /**
964
         * Weights primitive types before non-primitive types.
965
         */
966
        FOR_PRIMITIVE_TYPES {
1✔
967
            @Override
968
            protected boolean resolve(TypeDefinition typeDefinition) {
969
                return typeDefinition.isPrimitive();
1✔
970
            }
971
        },
972

973
        /**
974
         * Weights enumeration types before non-enumeration types.
975
         */
976
        FOR_ENUMERATION_TYPES {
1✔
977
            @Override
978
            protected boolean resolve(TypeDefinition typeDefinition) {
979
                return typeDefinition.isEnum();
1✔
980
            }
981
        },
982

983
        /**
984
         * Weights {@link String} types first.
985
         */
986
        FOR_STRING_TYPES {
1✔
987
            @Override
988
            protected boolean resolve(TypeDefinition typeDefinition) {
989
                return typeDefinition.represents(String.class);
1✔
990
            }
991
        },
992

993
        /**
994
         * Weights primitive wrapper types first.
995
         */
996
        FOR_PRIMITIVE_WRAPPER_TYPES {
1✔
997
            @Override
998
            protected boolean resolve(TypeDefinition typeDefinition) {
999
                return typeDefinition.asErasure().isPrimitiveWrapper();
1✔
1000
            }
1001
        };
1002

1003
        /**
1004
         * {@inheritDoc}
1005
         */
1006
        public int compare(FieldDescription.InDefinedShape left, FieldDescription.InDefinedShape right) {
1007
            if (resolve(left.getType()) && !resolve(right.getType())) {
1✔
1008
                return -1;
1✔
1009
            } else if (!resolve(left.getType()) && resolve(right.getType())) {
1✔
1010
                return 1;
1✔
1011
            } else {
1012
                return 0;
1✔
1013
            }
1014
        }
1015

1016
        /**
1017
         * Resolves a type property.
1018
         *
1019
         * @param typeDefinition The type to resolve the property for.
1020
         * @return {@code true} if the type property is resolved.
1021
         */
1022
        protected abstract boolean resolve(TypeDefinition typeDefinition);
1023
    }
1024

1025
    /**
1026
     * A compound comparator that compares the values of multiple fields.
1027
     */
1028
    @HashCodeAndEqualsPlugin.Enhance
1029
    @SuppressFBWarnings(value = "SE_COMPARATOR_SHOULD_BE_SERIALIZABLE", justification = "Not used within a serializable instance")
1030
    protected static class CompoundComparator implements Comparator<FieldDescription.InDefinedShape> {
1031

1032
        /**
1033
         * All comparators to be applied in the application order.
1034
         */
1035
        private final List<Comparator<? super FieldDescription.InDefinedShape>> comparators;
1036

1037
        /**
1038
         * Creates a compound comparator.
1039
         *
1040
         * @param comparator All comparators to be applied in the application order.
1041
         */
1042
        @SafeVarargsPlugin.Enhance
1043
        @SuppressWarnings("unchecked") // In absence of @SafeVarargs
1044
        protected CompoundComparator(Comparator<? super FieldDescription.InDefinedShape>... comparator) {
1045
            this(Arrays.asList(comparator));
1✔
1046
        }
1✔
1047

1048
        /**
1049
         * Creates a compound comparator.
1050
         *
1051
         * @param comparators All comparators to be applied in the application order.
1052
         */
1053
        protected CompoundComparator(List<? extends Comparator<? super FieldDescription.InDefinedShape>> comparators) {
1✔
1054
            this.comparators = new ArrayList<Comparator<? super FieldDescription.InDefinedShape>>();
1✔
1055
            for (Comparator<? super FieldDescription.InDefinedShape> comparator : comparators) {
1✔
1056
                if (comparator instanceof CompoundComparator) {
1✔
1057
                    this.comparators.addAll(((CompoundComparator) comparator).comparators);
1✔
1058
                } else if (!(comparator instanceof NaturalOrderComparator)) {
1✔
1059
                    this.comparators.add(comparator);
1✔
1060
                }
1061
            }
1✔
1062
        }
1✔
1063

1064
        /**
1065
         * {@inheritDoc}
1066
         */
1067
        public int compare(FieldDescription.InDefinedShape left, FieldDescription.InDefinedShape right) {
1068
            for (Comparator<? super FieldDescription.InDefinedShape> comparator : comparators) {
1✔
1069
                int comparison = comparator.compare(left, right);
1✔
1070
                if (comparison != 0) {
1✔
1071
                    return comparison;
1✔
1072
                }
1073
            }
1✔
1074
            return 0;
1✔
1075
        }
1076
    }
1077
}
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