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

raphw / byte-buddy / #752

20 Mar 2025 05:23PM UTC coverage: 85.175% (+0.005%) from 85.17%
#752

push

raphw
Add checksums

29243 of 34333 relevant lines covered (85.17%)

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.*;
42

43
import static net.bytebuddy.matcher.ElementMatchers.*;
44

45
/**
46
 * An implementation of {@link Object#equals(Object)} that takes a class's declared fields into consideration. Equality is resolved by comparing two
47
 * 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
48
 * the instance upon which the method is invoked returns {@code true} upon calling the value's {@code equals} method. For arrays, the corresponding
49
 * utilities of {@link java.util.Arrays} are used.
50
 */
51
@HashCodeAndEqualsPlugin.Enhance
52
public class EqualsMethod implements Implementation {
53

54
    /**
55
     * The {@link Object#equals(Object)} method.
56
     */
57
    private static final MethodDescription.InDefinedShape EQUALS = TypeDescription.ForLoadedType.of(Object.class)
1✔
58
            .getDeclaredMethods()
1✔
59
            .filter(isEquals())
1✔
60
            .getOnly();
1✔
61

62
    /**
63
     * The baseline equality to check.
64
     */
65
    private final SuperClassCheck superClassCheck;
66

67
    /**
68
     * The instance type compatibility check.
69
     */
70
    private final TypeCompatibilityCheck typeCompatibilityCheck;
71

72
    /**
73
     * A matcher to filter fields that should not be used for a equality resolution.
74
     */
75
    private final ElementMatcher.Junction<? super FieldDescription.InDefinedShape> ignored;
76

77
    /**
78
     * A matcher to determine fields of a reference type that cannot be {@code null}.
79
     */
80
    private final ElementMatcher.Junction<? super FieldDescription.InDefinedShape> nonNullable;
81

82
    /**
83
     * A matcher to determine that a field should be considered by its identity.
84
     */
85
    private final ElementMatcher.Junction<? super FieldDescription.InDefinedShape> identity;
86

87
    /**
88
     * The comparator to apply for ordering fields.
89
     */
90
    private final Comparator<? super FieldDescription.InDefinedShape> comparator;
91

92
    /**
93
     * Creates a new equals method implementation.
94
     *
95
     * @param superClassCheck The baseline equality to check.
96
     */
97
    protected EqualsMethod(SuperClassCheck superClassCheck) {
98
        this(superClassCheck, TypeCompatibilityCheck.EXACT, none(), none(), none(), NaturalOrderComparator.INSTANCE);
1✔
99
    }
1✔
100

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

125
    /**
126
     * Creates an equals implementation that invokes the super class's {@link Object#equals(Object)} method first.
127
     *
128
     * @return An equals implementation that invokes the super class's {@link Object#equals(Object)} method first.
129
     */
130
    public static EqualsMethod requiringSuperClassEquality() {
131
        return new EqualsMethod(SuperClassCheck.ENABLED);
1✔
132
    }
133

134
    /**
135
     * Creates an equals method implementation that does not invoke the super class's {@link Object#equals(Object)} method.
136
     *
137
     * @return An equals method implementation that does not invoke the super class's {@link Object#equals(Object)} method.
138
     */
139
    public static EqualsMethod isolated() {
140
        return new EqualsMethod(SuperClassCheck.DISABLED);
1✔
141
    }
142

143
    /**
144
     * Returns a new version of this equals method implementation that ignores the specified fields additionally to any
145
     * previously specified fields.
146
     *
147
     * @param ignored A matcher to specify any fields that should be ignored.
148
     * @return A new version of this equals method implementation that also ignores any fields matched by the provided matcher.
149
     */
150
    public EqualsMethod withIgnoredFields(ElementMatcher<? super FieldDescription.InDefinedShape> ignored) {
151
        return new EqualsMethod(superClassCheck, typeCompatibilityCheck, this.ignored.<FieldDescription.InDefinedShape>or(ignored), nonNullable, identity, comparator);
1✔
152
    }
153

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

166
    /**
167
     * Returns a new version of this equals method implementation that considers the matched fields by their identity.
168
     *
169
     * @param identity A matcher to determine that a field should be considered by its identity.
170
     * @return A new version of this equals method implementation that also considers the matched fields by their identity.
171
     */
172
    public EqualsMethod withIdentityFields(ElementMatcher<? super FieldDescription.InDefinedShape> identity) {
173
        return new EqualsMethod(superClassCheck, typeCompatibilityCheck, ignored, nonNullable, this.identity.<FieldDescription.InDefinedShape>or(identity), comparator);
1✔
174
    }
175

176
    /**
177
     * Returns a new version of this equals method that compares fields with primitive types prior to fields with non-primitive types.
178
     *
179
     * @return A new version of this equals method that compares primitive-typed fields before fields with non-primitive-typed fields.
180
     */
181
    public EqualsMethod withPrimitiveTypedFieldsFirst() {
182
        return withFieldOrder(TypePropertyComparator.FOR_PRIMITIVE_TYPES);
1✔
183
    }
184

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

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

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

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

224
    /**
225
     * Returns a new version of this equals method implementation that permits subclasses of the instrumented type to be equal to instances
226
     * of the instrumented type instead of requiring an exact match.
227
     *
228
     * @return A new version of this equals method implementation that permits subclasses of the instrumented type to be equal to instances
229
     * of the instrumented type instead of requiring an exact match.
230
     */
231
    public EqualsMethod withSubclassEquality() {
232
        return new EqualsMethod(superClassCheck, TypeCompatibilityCheck.SUBCLASS, ignored, nonNullable, identity, comparator);
1✔
233
    }
234

235
    /**
236
     * {@inheritDoc}
237
     */
238
    public InstrumentedType prepare(InstrumentedType instrumentedType) {
239
        return instrumentedType;
1✔
240
    }
241

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

262
    /**
263
     * Checks the equality contract against the super class.
264
     */
265
    protected enum SuperClassCheck {
1✔
266

267
        /**
268
         * Does not perform any super class check.
269
         */
270
        DISABLED {
1✔
271
            @Override
272
            protected StackManipulation resolve(TypeDescription instrumentedType) {
273
                return StackManipulation.Trivial.INSTANCE;
1✔
274
            }
275
        },
276

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

294
        /**
295
         * Resolves a stack manipulation for the required super class check.
296
         *
297
         * @param instrumentedType The instrumented type.
298
         * @return A stack manipulation that implements the specified check.
299
         */
300
        protected abstract StackManipulation resolve(TypeDescription instrumentedType);
301
    }
302

303
    /**
304
     * Checks the overall type of the provided argument.
305
     */
306
    protected enum TypeCompatibilityCheck {
1✔
307

308
        /**
309
         * Requires an exact type match.
310
         */
311
        EXACT {
1✔
312
            @Override
313
            public StackManipulation resolve(TypeDescription instrumentedType) {
314
                return new StackManipulation.Compound(
1✔
315
                        MethodVariableAccess.REFERENCE.loadFrom(1),
1✔
316
                        ConditionalReturn.onNullValue(),
1✔
317
                        MethodVariableAccess.REFERENCE.loadFrom(0),
1✔
318
                        MethodInvocation.invoke(GET_CLASS),
1✔
319
                        MethodVariableAccess.REFERENCE.loadFrom(1),
1✔
320
                        MethodInvocation.invoke(GET_CLASS),
1✔
321
                        ConditionalReturn.onNonIdentity()
1✔
322
                );
323
            }
324
        },
325

326
        /**
327
         * Requires a subtype relationship.
328
         */
329
        SUBCLASS {
1✔
330
            @Override
331
            protected StackManipulation resolve(TypeDescription instrumentedType) {
332
                return new StackManipulation.Compound(
1✔
333
                        MethodVariableAccess.REFERENCE.loadFrom(1),
1✔
334
                        InstanceCheck.of(instrumentedType),
1✔
335
                        ConditionalReturn.onZeroInteger()
1✔
336
                );
337
            }
338
        };
339

340
        /**
341
         * The {@link Object#getClass()} method.
342
         */
343
        protected static final MethodDescription.InDefinedShape GET_CLASS = TypeDescription.ForLoadedType.of(Object.class)
1✔
344
                .getDeclaredMethods()
1✔
345
                .filter(named("getClass"))
1✔
346
                .getOnly();
1✔
347

348
        /**
349
         * Resolves a stack manipulation for the required type compatibility check.
350
         *
351
         * @param instrumentedType The instrumented type.
352
         * @return A stack manipulation that implements the specified check.
353
         */
354
        protected abstract StackManipulation resolve(TypeDescription instrumentedType);
355
    }
356

357
    /**
358
     * Guards a field value against a potential {@code null} value.
359
     */
360
    protected interface NullValueGuard {
361

362
        /**
363
         * Returns a stack manipulation to apply before computing equality.
364
         *
365
         * @return A stack manipulation to apply before computing equality.
366
         */
367
        StackManipulation before();
368

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

376
        /**
377
         * Returns the required padding for the local variable array to apply this guard.
378
         *
379
         * @return The required padding for the local variable array to apply this guard.
380
         */
381
        int getRequiredVariablePadding();
382

383
        /**
384
         * A non-operational null value guard.
385
         */
386
        enum NoOp implements NullValueGuard {
1✔
387

388
            /**
389
             * The singleton instance.
390
             */
391
            INSTANCE;
1✔
392

393
            /**
394
             * {@inheritDoc}
395
             */
396
            public StackManipulation before() {
397
                return StackManipulation.Trivial.INSTANCE;
1✔
398
            }
399

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

407
            /**
408
             * {@inheritDoc}
409
             */
410
            public int getRequiredVariablePadding() {
411
                return StackSize.ZERO.getSize();
1✔
412
            }
413
        }
414

415
        /**
416
         * A null value guard that expects a reference type and that skips the comparison if both values are {@code null} but returns if
417
         * the invoked instance's field value is {@code null} but not the compared instance's value.
418
         */
419
        @HashCodeAndEqualsPlugin.Enhance
420
        class UsingJump implements NullValueGuard {
421

422
            /**
423
             * The instrumented method.
424
             */
425
            private final MethodDescription instrumentedMethod;
426

427
            /**
428
             * The label to jump to if the first value is {@code null} whereas the second value is not {@code null}.
429
             */
430
            private final Label firstValueNull;
431

432
            /**
433
             * The label to jump to if the second value is {@code null}.
434
             */
435
            private final Label secondValueNull;
436

437
            /**
438
             * A label indicating the end of the null-guarding block.
439
             */
440
            private final Label endOfBlock;
441

442
            /**
443
             * Creates a new null value guard using a jump instruction for {@code null} values.
444
             *
445
             * @param instrumentedMethod The instrumented method.
446
             */
447
            protected UsingJump(MethodDescription instrumentedMethod) {
1✔
448
                this.instrumentedMethod = instrumentedMethod;
1✔
449
                firstValueNull = new Label();
1✔
450
                secondValueNull = new Label();
1✔
451
                endOfBlock = new Label();
1✔
452
            }
1✔
453

454
            /**
455
             * {@inheritDoc}
456
             */
457
            public StackManipulation before() {
458
                return new UsingJump.BeforeInstruction();
1✔
459
            }
460

461
            /**
462
             * {@inheritDoc}
463
             */
464
            public StackManipulation after() {
465
                return new UsingJump.AfterInstruction();
1✔
466
            }
467

468
            /**
469
             * {@inheritDoc}
470
             */
471
            public int getRequiredVariablePadding() {
472
                return 2;
1✔
473
            }
474

475
            /**
476
             * The stack manipulation to apply before the equality computation.
477
             */
478
            @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
479
            protected class BeforeInstruction extends StackManipulation.AbstractBase {
1✔
480

481
                /**
482
                 * {@inheritDoc}
483
                 */
484
                public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
485
                    methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize());
1✔
486
                    methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize() + 1);
1✔
487
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize() + 1);
1✔
488
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize());
1✔
489
                    methodVisitor.visitJumpInsn(Opcodes.IFNULL, secondValueNull);
1✔
490
                    methodVisitor.visitJumpInsn(Opcodes.IFNULL, firstValueNull);
1✔
491
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize() + 1);
1✔
492
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize());
1✔
493
                    return Size.ZERO;
1✔
494
                }
495
            }
496

497
            /**
498
             * The stack manipulation to apply after the equality computation.
499
             */
500
            @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
501
            protected class AfterInstruction extends StackManipulation.AbstractBase {
1✔
502

503
                /**
504
                 * {@inheritDoc}
505
                 */
506
                public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
507
                    methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfBlock);
1✔
508
                    methodVisitor.visitLabel(secondValueNull);
1✔
509
                    implementationContext.getFrameGeneration().same1(methodVisitor,
1✔
510
                            TypeDescription.ForLoadedType.of(Object.class),
1✔
511
                            Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
1✔
512
                    methodVisitor.visitJumpInsn(Opcodes.IFNULL, endOfBlock);
1✔
513
                    methodVisitor.visitLabel(firstValueNull);
1✔
514
                    implementationContext.getFrameGeneration().same(methodVisitor,
1✔
515
                            Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
1✔
516
                    methodVisitor.visitInsn(Opcodes.ICONST_0);
1✔
517
                    methodVisitor.visitInsn(Opcodes.IRETURN);
1✔
518
                    methodVisitor.visitLabel(endOfBlock);
1✔
519
                    implementationContext.getFrameGeneration().same(methodVisitor,
1✔
520
                            Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
1✔
521
                    return Size.ZERO;
1✔
522
                }
523
            }
524
        }
525
    }
526

527
    /**
528
     * A value comparator is responsible to compare to values of a given type.
529
     */
530
    protected enum ValueComparator implements StackManipulation {
1✔
531

532
        /**
533
         * A comparator for a {@code long} value.
534
         */
535
        LONG {
1✔
536
            /** {@inheritDoc} */
537
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
538
                methodVisitor.visitInsn(Opcodes.LCMP);
1✔
539
                return new Size(-2, 0);
1✔
540
            }
541
        },
542

543
        /**
544
         * A comparator for a {@code float} value.
545
         */
546
        FLOAT {
1✔
547
            /** {@inheritDoc} */
548
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
549
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "compare", "(FF)I", false);
1✔
550
                return new Size(-1, 0);
1✔
551
            }
552
        },
553

554
        /**
555
         * A comparator for a {@code double} value.
556
         */
557
        DOUBLE {
1✔
558
            /** {@inheritDoc} */
559
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
560
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "compare", "(DD)I", false);
1✔
561
                return new Size(-2, 0);
1✔
562
            }
563
        },
564

565
        /**
566
         * A comparator for a {@code boolean[]} value.
567
         */
568
        BOOLEAN_ARRAY {
1✔
569
            /** {@inheritDoc} */
570
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
571
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([Z[Z)Z", false);
1✔
572
                return new Size(-1, 0);
1✔
573
            }
574
        },
575

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

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

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

609
        /**
610
         * A comparator for an {@code int[]} value.
611
         */
612
        INTEGER_ARRAY {
1✔
613
            /** {@inheritDoc} */
614
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
615
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([I[I)Z", false);
1✔
616
                return new Size(-1, 0);
1✔
617
            }
618
        },
619

620
        /**
621
         * A comparator for a {@code long[]} value.
622
         */
623
        LONG_ARRAY {
1✔
624
            /** {@inheritDoc} */
625
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
626
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([J[J)Z", false);
1✔
627
                return new Size(-1, 0);
1✔
628
            }
629
        },
630

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

642
        /**
643
         * A transformer for a {@code double[]} value.
644
         */
645
        DOUBLE_ARRAY {
1✔
646
            /** {@inheritDoc} */
647
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
648
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([D[D)Z", false);
1✔
649
                return new Size(-1, 0);
1✔
650
            }
651
        },
652

653
        /**
654
         * A transformer for a reference array value.
655
         */
656
        REFERENCE_ARRAY {
1✔
657
            /** {@inheritDoc} */
658
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
659
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([Ljava/lang/Object;[Ljava/lang/Object;)Z", false);
1✔
660
                return new Size(-1, 0);
1✔
661
            }
662
        },
663

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

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

720
        /**
721
         * {@inheritDoc}
722
         */
723
        public boolean isValid() {
724
            return true;
×
725
        }
726
    }
727

728
    /**
729
     * A byte code appender to implement the {@link EqualsMethod}.
730
     */
731
    @HashCodeAndEqualsPlugin.Enhance
732
    protected static class Appender implements ByteCodeAppender {
733

734
        /**
735
         * The instrumented type.
736
         */
737
        private final TypeDescription instrumentedType;
738

739
        /**
740
         * The baseline stack manipulation.
741
         */
742
        private final StackManipulation baseline;
743

744
        /**
745
         * A list of fields to use for the comparison.
746
         */
747
        private final List<FieldDescription.InDefinedShape> fieldDescriptions;
748

749
        /**
750
         * A matcher to determine fields of a reference type that cannot be {@code null}.
751
         */
752
        private final ElementMatcher<? super FieldDescription.InDefinedShape> nonNullable;
753

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

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

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

818
    /**
819
     * A conditional return aborts the equality computation if a given condition was reached.
820
     */
821
    @HashCodeAndEqualsPlugin.Enhance
822
    protected static class ConditionalReturn extends StackManipulation.AbstractBase {
823

824
        /**
825
         * The conditional jump instruction upon which the return is not triggered.
826
         */
827
        private final int jumpCondition;
828

829
        /**
830
         * The opcode for the value being returned.
831
         */
832
        private final int value;
833

834
        /**
835
         * Creates a conditional return for a value of {@code false}.
836
         *
837
         * @param jumpCondition The opcode upon which the return is not triggered.
838
         */
839
        protected ConditionalReturn(int jumpCondition) {
840
            this(jumpCondition, Opcodes.ICONST_0);
1✔
841
        }
1✔
842

843
        /**
844
         * Creates a conditional return.
845
         *
846
         * @param jumpCondition The opcode upon which the return is not triggered.
847
         * @param value         The opcode for the value being returned.
848
         */
849
        private ConditionalReturn(int jumpCondition, int value) {
1✔
850
            this.jumpCondition = jumpCondition;
1✔
851
            this.value = value;
1✔
852
        }
1✔
853

854
        /**
855
         * Returns a conditional return that returns on an {@code int} value of {@code 0}.
856
         *
857
         * @return A conditional return that returns on an {@code int} value of {@code 0}.
858
         */
859
        protected static ConditionalReturn onZeroInteger() {
860
            return new ConditionalReturn(Opcodes.IFNE);
1✔
861
        }
862

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

872
        /**
873
         * Returns a conditional return that returns on a reference value of {@code null}.
874
         *
875
         * @return A conditional return that returns on a reference value of {@code null}.
876
         */
877
        protected static ConditionalReturn onNullValue() {
878
            return new ConditionalReturn(Opcodes.IFNONNULL);
1✔
879
        }
880

881
        /**
882
         * Returns a conditional return that returns if two reference values are not identical.
883
         *
884
         * @return A conditional return that returns if two reference values are not identical.
885
         */
886
        protected static ConditionalReturn onNonIdentity() {
887
            return new ConditionalReturn(Opcodes.IF_ACMPEQ);
1✔
888
        }
889

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

899
        /**
900
         * Returns a conditional return that returns if two {@code int} values are not equal.
901
         *
902
         * @return A conditional return that returns if two {@code int} values are not equal.
903
         */
904
        protected static ConditionalReturn onNonEqualInteger() {
905
            return new ConditionalReturn(Opcodes.IF_ICMPEQ);
1✔
906
        }
907

908
        /**
909
         * Returns a new stack manipulation that returns {@code true} for the given condition.
910
         *
911
         * @return A new stack manipulation that returns {@code true} for the given condition.
912
         */
913
        protected StackManipulation returningTrue() {
914
            return new ConditionalReturn(jumpCondition, Opcodes.ICONST_1);
1✔
915
        }
916

917
        /**
918
         * {@inheritDoc}
919
         */
920
        public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
921
            Label label = new Label();
1✔
922
            methodVisitor.visitJumpInsn(jumpCondition, label);
1✔
923
            methodVisitor.visitInsn(value);
1✔
924
            methodVisitor.visitInsn(Opcodes.IRETURN);
1✔
925
            methodVisitor.visitLabel(label);
1✔
926
            implementationContext.getFrameGeneration().same(methodVisitor,
1✔
927
                    Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
1✔
928
            return new Size(-1, 1);
1✔
929
        }
930
    }
931

932
    /**
933
     * A comparator that retains the natural order.
934
     */
935
    protected enum NaturalOrderComparator implements Comparator<FieldDescription.InDefinedShape> {
1✔
936

937
        /**
938
         * The singleton instance.
939
         */
940
        INSTANCE;
1✔
941

942
        /**
943
         * {@inheritDoc}
944
         */
945
        public int compare(FieldDescription.InDefinedShape left, FieldDescription.InDefinedShape right) {
946
            return 0;
1✔
947
        }
948
    }
949

950
    /**
951
     * A comparator that sorts fields by a type property.
952
     */
953
    protected enum TypePropertyComparator implements Comparator<FieldDescription.InDefinedShape> {
1✔
954

955
        /**
956
         * Weights primitive types before non-primitive types.
957
         */
958
        FOR_PRIMITIVE_TYPES {
1✔
959
            @Override
960
            protected boolean resolve(TypeDefinition typeDefinition) {
961
                return typeDefinition.isPrimitive();
1✔
962
            }
963
        },
964

965
        /**
966
         * Weights enumeration types before non-enumeration types.
967
         */
968
        FOR_ENUMERATION_TYPES {
1✔
969
            @Override
970
            protected boolean resolve(TypeDefinition typeDefinition) {
971
                return typeDefinition.isEnum();
1✔
972
            }
973
        },
974

975
        /**
976
         * Weights {@link String} types first.
977
         */
978
        FOR_STRING_TYPES {
1✔
979
            @Override
980
            protected boolean resolve(TypeDefinition typeDefinition) {
981
                return typeDefinition.represents(String.class);
1✔
982
            }
983
        },
984

985
        /**
986
         * Weights primitive wrapper types first.
987
         */
988
        FOR_PRIMITIVE_WRAPPER_TYPES {
1✔
989
            @Override
990
            protected boolean resolve(TypeDefinition typeDefinition) {
991
                return typeDefinition.asErasure().isPrimitiveWrapper();
1✔
992
            }
993
        };
994

995
        /**
996
         * {@inheritDoc}
997
         */
998
        public int compare(FieldDescription.InDefinedShape left, FieldDescription.InDefinedShape right) {
999
            if (resolve(left.getType()) && !resolve(right.getType())) {
1✔
1000
                return -1;
1✔
1001
            } else if (!resolve(left.getType()) && resolve(right.getType())) {
1✔
1002
                return 1;
1✔
1003
            } else {
1004
                return 0;
1✔
1005
            }
1006
        }
1007

1008
        /**
1009
         * Resolves a type property.
1010
         *
1011
         * @param typeDefinition The type to resolve the property for.
1012
         * @return {@code true} if the type property is resolved.
1013
         */
1014
        protected abstract boolean resolve(TypeDefinition typeDefinition);
1015
    }
1016

1017
    /**
1018
     * A compound comparator that compares the values of multiple fields.
1019
     */
1020
    @HashCodeAndEqualsPlugin.Enhance
1021
    @SuppressFBWarnings(value = "SE_COMPARATOR_SHOULD_BE_SERIALIZABLE", justification = "Not used within a serializable instance")
1022
    protected static class CompoundComparator implements Comparator<FieldDescription.InDefinedShape> {
1023

1024
        /**
1025
         * All comparators to be applied in the application order.
1026
         */
1027
        private final List<Comparator<? super FieldDescription.InDefinedShape>> comparators;
1028

1029
        /**
1030
         * Creates a compound comparator.
1031
         *
1032
         * @param comparator All comparators to be applied in the application order.
1033
         */
1034
        @SafeVarargsPlugin.Enhance
1035
        @SuppressWarnings("unchecked") // In absence of @SafeVarargs
1036
        protected CompoundComparator(Comparator<? super FieldDescription.InDefinedShape>... comparator) {
1037
            this(Arrays.asList(comparator));
1✔
1038
        }
1✔
1039

1040
        /**
1041
         * Creates a compound comparator.
1042
         *
1043
         * @param comparators All comparators to be applied in the application order.
1044
         */
1045
        protected CompoundComparator(List<? extends Comparator<? super FieldDescription.InDefinedShape>> comparators) {
1✔
1046
            this.comparators = new ArrayList<Comparator<? super FieldDescription.InDefinedShape>>();
1✔
1047
            for (Comparator<? super FieldDescription.InDefinedShape> comparator : comparators) {
1✔
1048
                if (comparator instanceof CompoundComparator) {
1✔
1049
                    this.comparators.addAll(((CompoundComparator) comparator).comparators);
1✔
1050
                } else if (!(comparator instanceof NaturalOrderComparator)) {
1✔
1051
                    this.comparators.add(comparator);
1✔
1052
                }
1053
            }
1✔
1054
        }
1✔
1055

1056
        /**
1057
         * {@inheritDoc}
1058
         */
1059
        public int compare(FieldDescription.InDefinedShape left, FieldDescription.InDefinedShape right) {
1060
            for (Comparator<? super FieldDescription.InDefinedShape> comparator : comparators) {
1✔
1061
                int comparison = comparator.compare(left, right);
1✔
1062
                if (comparison != 0) {
1✔
1063
                    return comparison;
1✔
1064
                }
1065
            }
1✔
1066
            return 0;
1✔
1067
        }
1068
    }
1069
}
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