/
FieldAccessor.java
984 lines (863 loc) · 40.4 KB
/
FieldAccessor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
package net.bytebuddy.implementation;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.modifier.ModifierContributor;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.TargetType;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.utility.ByteBuddyCommons;
import org.objectweb.asm.MethodVisitor;
import static net.bytebuddy.matcher.ElementMatchers.*;
import static net.bytebuddy.utility.ByteBuddyCommons.*;
/**
* Defines a method to access a given field by following the Java bean conventions for getters and setters:
* <ul>
* <li>Getter: A method named {@code getFoo()} will be instrumented to read and return the value of a field {@code foo}
* or another field if one was specified explicitly. If a property is of type {@link java.lang.Boolean} or
* {@code boolean}, the name {@code isFoo()} is also permitted.</li>
* <li>Setter: A method named {@code setFoo(value)} will be instrumented to write the given argument {@code value}
* to a field {@code foo} or to another field if one was specified explicitly.</li>
* </ul>
*/
public abstract class FieldAccessor implements Implementation {
/**
* The assigner to use.
*/
protected final Assigner assigner;
/**
* {@code true} if the runtime type of the field's value should be considered when a field
* is accessed.
*/
protected final boolean dynamicallyTyped;
/**
* Creates a new field accessor.
*
* @param assigner The assigner to use.
* @param dynamicallyTyped {@code true} if a field value's runtime type should be considered.
*/
protected FieldAccessor(Assigner assigner, boolean dynamicallyTyped) {
this.assigner = assigner;
this.dynamicallyTyped = dynamicallyTyped;
}
/**
* Defines a field accessor where any access is targeted to a field named {@code name}.
*
* @param name The name of the field to be accessed.
* @return A field accessor for a field of a given name.
*/
public static FieldDefinable ofField(String name) {
return new ForNamedField(Assigner.DEFAULT, Assigner.STATICALLY_TYPED, isValidIdentifier(name));
}
/**
* Defines a field accessor where any access is targeted to a field that matches the methods
* name with the Java specification for bean properties, i.e. a method {@code getFoo} or {@code setFoo(value)}
* will either read or write a field named {@code foo}.
*
* @return A field accessor that follows the Java naming conventions for bean properties.
*/
public static OwnerTypeLocatable ofBeanProperty() {
return of(FieldNameExtractor.ForBeanProperty.INSTANCE);
}
/**
* Defines a custom strategy for determining the field that is accessed by this field accessor.
*
* @param fieldNameExtractor The field name extractor to use.
* @return A field accessor using the given field name extractor.
*/
public static OwnerTypeLocatable of(FieldNameExtractor fieldNameExtractor) {
return new ForUnnamedField(Assigner.DEFAULT, Assigner.STATICALLY_TYPED, nonNull(fieldNameExtractor));
}
/**
* Applies a field getter implementation.
*
* @param methodVisitor The method visitor to write any instructions to.
* @param implementationContext The implementation context of the current implementation.
* @param fieldDescription The description of the field to read.
* @param methodDescription The method that is target of the implementation.
* @return The required size of the operand stack and local variable array for this implementation.
*/
protected ByteCodeAppender.Size applyGetter(MethodVisitor methodVisitor,
Implementation.Context implementationContext,
FieldDescription fieldDescription,
MethodDescription methodDescription) {
StackManipulation stackManipulation = assigner.assign(fieldDescription.getFieldType(),
methodDescription.getReturnType(),
dynamicallyTyped);
if (!stackManipulation.isValid()) {
throw new IllegalStateException("Getter type of " + methodDescription + " is not compatible with " + fieldDescription);
}
return apply(methodVisitor,
implementationContext,
fieldDescription,
methodDescription,
new StackManipulation.Compound(
FieldAccess.forField(fieldDescription).getter(),
stackManipulation
)
);
}
/**
* Applies a field setter implementation.
*
* @param methodVisitor The method visitor to write any instructions to.
* @param implementationContext The implementation context of the current implementation.
* @param fieldDescription The description of the field to write to.
* @param methodDescription The method that is target of the instrumentation.
* @return The required size of the operand stack and local variable array for this implementation.
*/
protected ByteCodeAppender.Size applySetter(MethodVisitor methodVisitor,
Implementation.Context implementationContext,
FieldDescription fieldDescription,
MethodDescription methodDescription) {
StackManipulation stackManipulation = assigner.assign(methodDescription.getParameters().get(0).getType(),
fieldDescription.getFieldType(),
dynamicallyTyped);
if (!stackManipulation.isValid()) {
throw new IllegalStateException("Setter type of " + methodDescription + " is not compatible with " + fieldDescription);
} else if (fieldDescription.isFinal()) {
throw new IllegalArgumentException("Cannot apply setter on final field " + fieldDescription);
}
return apply(methodVisitor,
implementationContext,
fieldDescription,
methodDescription,
new StackManipulation.Compound(
MethodVariableAccess.forType(fieldDescription.getFieldType())
.loadOffset(methodDescription.getParameters().get(0).getOffset()),
stackManipulation,
FieldAccess.forField(fieldDescription).putter()
)
);
}
/**
* A generic implementation of the application of a {@link net.bytebuddy.implementation.bytecode.ByteCodeAppender}.
*
* @param methodVisitor The method visitor to write any instructions to.
* @param implementationContext The implementation context of the current implementation.
* @param fieldDescription The description of the field to access.
* @param methodDescription The method that is target of the implementation.
* @param fieldAccess The manipulation that represents the field access.
* @return A suitable {@link net.bytebuddy.implementation.bytecode.ByteCodeAppender}.
*/
private ByteCodeAppender.Size apply(MethodVisitor methodVisitor,
Implementation.Context implementationContext,
FieldDescription fieldDescription,
MethodDescription methodDescription,
StackManipulation fieldAccess) {
if (methodDescription.isStatic() && !fieldDescription.isStatic()) {
throw new IllegalArgumentException("Cannot call instance field "
+ fieldDescription + " from static method " + methodDescription);
}
StackManipulation.Size stackSize = new StackManipulation.Compound(
fieldDescription.isStatic()
? StackManipulation.LegalTrivial.INSTANCE
: MethodVariableAccess.REFERENCE.loadOffset(0),
fieldAccess,
MethodReturn.returning(methodDescription.getReturnType())
).apply(methodVisitor, implementationContext);
return new ByteCodeAppender.Size(stackSize.getMaximalSize(), methodDescription.getStackSize());
}
/**
* Locates a field's name.
*
* @param targetMethod The method that is target of the implementation.
* @return The name of the field to be located for this implementation.
*/
protected abstract String getFieldName(MethodDescription targetMethod);
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& dynamicallyTyped == ((FieldAccessor) other).dynamicallyTyped
&& assigner.equals(((FieldAccessor) other).assigner);
}
@Override
public int hashCode() {
return 31 * assigner.hashCode() + (dynamicallyTyped ? 1 : 0);
}
/**
* A field locator allows to determine a field name for a given method.
*/
public interface FieldLocator {
/**
* Locates a field of a given name or throws an exception if no field with such a name exists.
*
* @param name The name of the field to locate.
* @return A representation of this field.
*/
FieldDescription locate(String name);
/**
* A factory that only looks up fields in the instrumented type.
*/
enum ForInstrumentedType implements Factory {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public FieldLocator make(TypeDescription instrumentedType) {
return new ForGivenType(instrumentedType, instrumentedType);
}
@Override
public String toString() {
return "FieldAccessor.FieldLocator.ForInstrumentedType." + name();
}
}
/**
* A factory for creating a {@link net.bytebuddy.implementation.FieldAccessor.FieldLocator}.
*/
interface Factory {
/**
* Creates a field locator.
*
* @param instrumentedType The instrumented type onto which the field locator is to be applied.
* @return The field locator for locating fields on a given type.
*/
FieldLocator make(TypeDescription instrumentedType);
}
/**
* A field locator that finds a type by traversing the type hierarchy beginning with fields defined
* in the most specific subclass traversing the class hierarchy down to the least specific type.
* This emulates the Java language's field access where fields are shadowed when an extending class defines
* a field with identical name.
*/
class ForInstrumentedTypeHierarchy implements FieldLocator {
/**
* The instrumented type for which a field is located.
*/
private final TypeDescription instrumentedType;
/**
* Creates a field locator that follows the type hierarchy.
*
* @param instrumentedType The instrumented type onto which the field locator is to be applied.
*/
public ForInstrumentedTypeHierarchy(TypeDescription instrumentedType) {
this.instrumentedType = instrumentedType;
}
@Override
public FieldDescription locate(String name) {
TypeDescription currentType = instrumentedType;
do {
FieldList fieldList = currentType.getDeclaredFields().filter(named(name).and(isVisibleTo(instrumentedType)));
if (fieldList.size() == 1) {
return fieldList.getOnly();
}
} while (!(currentType = currentType.getSuperType()).represents(Object.class));
throw new IllegalArgumentException("There is no field '" + name + " that is visible to " + instrumentedType);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& instrumentedType.equals(((ForInstrumentedTypeHierarchy) other).instrumentedType);
}
@Override
public int hashCode() {
return instrumentedType.hashCode();
}
@Override
public String toString() {
return "FieldAccessor.FieldLocator.ForInstrumentedTypeHierarchy{instrumentedType=" + instrumentedType + '}';
}
/**
* A field locator factory creating a
* {@link net.bytebuddy.implementation.FieldAccessor.FieldLocator.ForInstrumentedTypeHierarchy}.
*/
public enum Factory implements FieldLocator.Factory {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public FieldLocator make(TypeDescription instrumentedType) {
return new ForInstrumentedTypeHierarchy(instrumentedType);
}
@Override
public String toString() {
return "FieldAccessor.FieldLocator.ForInstrumentedTypeHierarchy.Factory." + name();
}
}
}
/**
* A field locator that only looks up fields that are defined for a given type.
*/
class ForGivenType implements FieldLocator {
/**
* The target type for which a field should be accessed.
*/
private final TypeDescription targetType;
/**
* The instrumented type onto which the field locator is to be applied.
*/
private final TypeDescription instrumentedType;
/**
* Creates a new field locator for a given type.
*
* @param targetType The type for which fields are to be looked up.
* @param instrumentedType The instrumented type onto which the field locator is to be applied.
*/
public ForGivenType(TypeDescription targetType, TypeDescription instrumentedType) {
this.targetType = targetType;
this.instrumentedType = instrumentedType;
}
@Override
public FieldDescription locate(String name) {
FieldList fieldList = targetType.getDeclaredFields().filter(named(name).and(isVisibleTo(instrumentedType)));
if (fieldList.size() != 1) {
throw new IllegalArgumentException("No field named " + name + " on " + targetType + " is visible to " + instrumentedType);
}
return fieldList.getOnly();
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& instrumentedType.equals(((ForGivenType) other).instrumentedType)
&& targetType.equals(((ForGivenType) other).targetType);
}
@Override
public int hashCode() {
return 31 * instrumentedType.hashCode() + targetType.hashCode();
}
@Override
public String toString() {
return "FieldAccessor.FieldLocator.ForGivenType{" +
"targetType=" + targetType +
", instrumentedType=" + instrumentedType +
'}';
}
/**
* A factory for a field locator locating given type.
*/
public static class Factory implements FieldLocator.Factory {
/**
* The type to locate.
*/
private final TypeDescription targetType;
/**
* Creates a new field locator factory for a given type.
*
* @param targetType The type for which fields are to be looked up.
*/
public Factory(TypeDescription targetType) {
this.targetType = targetType;
}
@Override
public FieldLocator make(TypeDescription instrumentedType) {
return new ForGivenType(targetType, instrumentedType);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& targetType.equals(((Factory) other).targetType);
}
@Override
public int hashCode() {
return targetType.hashCode();
}
@Override
public String toString() {
return "FieldAccessor.FieldLocator.ForGivenType.Factory{targetType=" + targetType + '}';
}
}
}
}
/**
* A field name extractor is responsible for determining a field name to a method that is implemented
* to access this method.
*/
public interface FieldNameExtractor {
/**
* Extracts a field name to be accessed by a getter or setter method.
*
* @param methodDescription The method for which a field name is to be determined.
* @return The name of the field to be accessed by this method.
*/
String fieldNameFor(MethodDescription methodDescription);
/**
* A {@link net.bytebuddy.implementation.FieldAccessor.FieldNameExtractor} that determines a field name
* according to the rules of Java bean naming conventions.
*/
enum ForBeanProperty implements FieldNameExtractor {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public String fieldNameFor(MethodDescription methodDescription) {
String name = methodDescription.getInternalName();
int crop;
if (name.startsWith("get") || name.startsWith("set")) {
crop = 3;
} else if (name.startsWith("is")) {
crop = 2;
} else {
throw new IllegalArgumentException(methodDescription + " does not follow Java bean naming conventions");
}
name = name.substring(crop);
if (name.length() == 0) {
throw new IllegalArgumentException(methodDescription + " does not specify a bean name");
}
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
@Override
public String toString() {
return "FieldAccessor.FieldNameExtractor.ForBeanProperty." + name();
}
}
}
/**
* A field accessor that can be configured to use a given assigner and runtime type use configuration.
*/
public interface AssignerConfigurable extends Implementation {
/**
* Returns a field accessor that is identical to this field accessor but uses the given assigner
* and runtime type use configuration.
*
* @param assigner The assigner to use.
* @param dynamicallyTyped {@code true} if a field value's runtime type should be considered.
* @return This field accessor with the given assigner and runtime type use configuration.
*/
Implementation withAssigner(Assigner assigner, boolean dynamicallyTyped);
}
/**
* A field accessor that can be configured to locate a field in a specific manner.
*/
public interface OwnerTypeLocatable extends AssignerConfigurable {
/**
* Determines that a field should only be considered when it was defined in a given type.
*
* @param type The type to be considered.
* @return This field accessor which will only considered fields that are defined in the given type.
*/
AssignerConfigurable in(Class<?> type);
/**
* Determines that a field should only be considered when it was defined in a given type.
*
* @param typeDescription A description of the type to be considered.
* @return This field accessor which will only considered fields that are defined in the given type.
*/
AssignerConfigurable in(TypeDescription typeDescription);
/**
* Determines that a field should only be considered when it was identified by a field locator that is
* produced by the given factory.
*
* @param fieldLocatorFactory A factory that will produce a field locator that will be used to find locate
* a field to be accessed.
* @return This field accessor which will only considered fields that are defined in the given type.
*/
AssignerConfigurable in(FieldLocator.Factory fieldLocatorFactory);
}
/**
* Determines a field accessor that accesses a field of a given name which might not yet be
* defined.
*/
public interface FieldDefinable extends OwnerTypeLocatable {
/**
* Defines a field with the given name in the instrumented type.
*
* @param type The type of the field.
* @param modifier The modifiers for the field.
* @return A field accessor that defines a field of the given type.
*/
AssignerConfigurable defineAs(Class<?> type, ModifierContributor.ForField... modifier);
/**
* Defines a field with the given name in the instrumented type.
*
* @param typeDescription The type of the field.
* @param modifier The modifiers for the field.
* @return A field accessor that defines a field of the given type.
*/
AssignerConfigurable defineAs(TypeDescription typeDescription, ModifierContributor.ForField... modifier);
}
/**
* Implementation of a field accessor implementation where a field is identified by a method's name following
* the Java specification for bean properties.
*/
protected static class ForUnnamedField extends FieldAccessor implements OwnerTypeLocatable {
/**
* A factory for creating a field locator for implementing this field accessor.
*/
private final FieldLocator.Factory fieldLocatorFactory;
/**
* The field name extractor to be used.
*/
private final FieldNameExtractor fieldNameExtractor;
/**
* Creates a new field accessor implementation.
*
* @param assigner The assigner to use.
* @param dynamicallyTyped {@code true} if a field value's runtime type should be considered.
* @param fieldNameExtractor The field name extractor to use.
*/
protected ForUnnamedField(Assigner assigner,
boolean dynamicallyTyped,
FieldNameExtractor fieldNameExtractor) {
this(assigner,
dynamicallyTyped,
fieldNameExtractor,
FieldLocator.ForInstrumentedTypeHierarchy.Factory.INSTANCE);
}
/**
* Creates a new field accessor implementation.
*
* @param assigner The assigner to use.
* @param dynamicallyTyped {@code true} if a field value's runtime type should be considered.
* @param fieldNameExtractor The field name extractor to use.
* @param fieldLocatorFactory A factory that will produce a field locator that will be used to find locate
* a field to be accessed.
*/
protected ForUnnamedField(Assigner assigner,
boolean dynamicallyTyped,
FieldNameExtractor fieldNameExtractor,
FieldLocator.Factory fieldLocatorFactory) {
super(assigner, dynamicallyTyped);
this.fieldNameExtractor = fieldNameExtractor;
this.fieldLocatorFactory = fieldLocatorFactory;
}
@Override
public AssignerConfigurable in(FieldLocator.Factory fieldLocatorFactory) {
return new ForUnnamedField(assigner, dynamicallyTyped, fieldNameExtractor, nonNull(fieldLocatorFactory));
}
@Override
public AssignerConfigurable in(Class<?> type) {
return in(new TypeDescription.ForLoadedType(nonNull(type)));
}
@Override
public AssignerConfigurable in(TypeDescription typeDescription) {
return typeDescription.represents(TargetType.class)
? in(FieldLocator.ForInstrumentedType.INSTANCE)
: in(new FieldLocator.ForGivenType.Factory(typeDescription));
}
@Override
public Implementation withAssigner(Assigner assigner, boolean dynamicallyTyped) {
return new ForUnnamedField(nonNull(assigner), dynamicallyTyped, fieldNameExtractor, fieldLocatorFactory);
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(fieldLocatorFactory.make(implementationTarget.getTypeDescription()));
}
@Override
protected String getFieldName(MethodDescription targetMethod) {
return fieldNameExtractor.fieldNameFor(targetMethod);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& super.equals(other)
&& fieldNameExtractor.equals(((ForUnnamedField) other).fieldNameExtractor)
&& fieldLocatorFactory.equals(((ForUnnamedField) other).fieldLocatorFactory);
}
@Override
public int hashCode() {
return 31 * (31 * super.hashCode() + fieldLocatorFactory.hashCode()) + fieldNameExtractor.hashCode();
}
@Override
public String toString() {
return "FieldAccessor.ForUnnamedField{" +
"assigner=" + assigner +
"dynamicallyTyped=" + dynamicallyTyped +
"fieldLocatorFactory=" + fieldLocatorFactory +
"fieldNameExtractor=" + fieldNameExtractor +
'}';
}
}
/**
* Implementation of a field accessor implementation where the field name is given explicitly.
*/
protected static class ForNamedField extends FieldAccessor implements FieldDefinable {
/**
* The name of the field that is accessed.
*/
private final String fieldName;
/**
* The preparation handler for implementing this field accessor.
*/
private final PreparationHandler preparationHandler;
/**
* The field locator factory for implementing this field accessor.
*/
private final FieldLocator.Factory fieldLocatorFactory;
/**
* Creates a field accessor implementation for a field of a given name.
*
* @param assigner The assigner to use.
* @param dynamicallyTyped {@code true} if a field value's runtime type should be considered.
* @param fieldName The name of the field.
*/
protected ForNamedField(Assigner assigner,
boolean dynamicallyTyped,
String fieldName) {
super(assigner, dynamicallyTyped);
this.fieldName = fieldName;
preparationHandler = PreparationHandler.NoOp.INSTANCE;
fieldLocatorFactory = FieldLocator.ForInstrumentedTypeHierarchy.Factory.INSTANCE;
}
/**
* reates a field accessor implementation for a field of a given name.
*
* @param fieldName The name of the field.
* @param preparationHandler The preparation handler for potentially defining a field.
* @param fieldLocatorFactory A factory that will produce a field locator that will be used to find locate
* a field to be accessed.
* @param assigner The assigner to use.
* @param dynamicallyTyped {@code true} if a field value's runtime type should be considered.
*/
private ForNamedField(Assigner assigner,
boolean dynamicallyTyped,
String fieldName,
PreparationHandler preparationHandler,
FieldLocator.Factory fieldLocatorFactory) {
super(assigner, dynamicallyTyped);
this.fieldName = fieldName;
this.preparationHandler = preparationHandler;
this.fieldLocatorFactory = fieldLocatorFactory;
}
@Override
public AssignerConfigurable defineAs(Class<?> type, ModifierContributor.ForField... modifier) {
return defineAs(new TypeDescription.ForLoadedType(nonNull(type)), modifier);
}
@Override
public AssignerConfigurable defineAs(TypeDescription typeDescription, ModifierContributor.ForField... modifier) {
return new ForNamedField(assigner,
dynamicallyTyped,
fieldName,
PreparationHandler.FieldDefiner.of(fieldName, isActualType(typeDescription), nonNull(modifier)),
FieldLocator.ForInstrumentedType.INSTANCE);
}
@Override
public AssignerConfigurable in(FieldLocator.Factory fieldLocatorFactory) {
return new ForNamedField(assigner,
dynamicallyTyped,
fieldName,
preparationHandler,
nonNull(fieldLocatorFactory));
}
@Override
public AssignerConfigurable in(Class<?> type) {
return in(new TypeDescription.ForLoadedType(nonNull(type)));
}
@Override
public AssignerConfigurable in(TypeDescription typeDescription) {
return typeDescription.represents(TargetType.class)
? in(FieldLocator.ForInstrumentedType.INSTANCE)
: in(new FieldLocator.ForGivenType.Factory(typeDescription));
}
@Override
public Implementation withAssigner(Assigner assigner, boolean dynamicallyTyped) {
return new ForNamedField(nonNull(assigner),
dynamicallyTyped,
fieldName,
preparationHandler,
fieldLocatorFactory);
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return preparationHandler.prepare(instrumentedType);
}
@Override
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(fieldLocatorFactory.make(implementationTarget.getTypeDescription()));
}
@Override
protected String getFieldName(MethodDescription targetMethod) {
return fieldName;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
if (!super.equals(other)) return false;
ForNamedField that = (ForNamedField) other;
return fieldLocatorFactory.equals(that.fieldLocatorFactory)
&& fieldName.equals(that.fieldName)
&& preparationHandler.equals(that.preparationHandler);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + fieldName.hashCode();
result = 31 * result + preparationHandler.hashCode();
result = 31 * result + fieldLocatorFactory.hashCode();
return result;
}
@Override
public String toString() {
return "FieldAccessor.ForNamedField{" +
"assigner=" + assigner +
"dynamicallyTyped=" + dynamicallyTyped +
"fieldName='" + fieldName + '\'' +
", preparationHandler=" + preparationHandler +
", fieldLocatorFactory=" + fieldLocatorFactory +
'}';
}
/**
* A preparation handler is responsible for defining a field value on an implementation, if necessary.
*/
protected interface PreparationHandler {
/**
* Prepares the instrumented type.
*
* @param instrumentedType The instrumented type to be prepared.
* @return The readily prepared instrumented type.
*/
InstrumentedType prepare(InstrumentedType instrumentedType);
/**
* A non-operational preparation handler that does not alter the field.
*/
enum NoOp implements PreparationHandler {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public String toString() {
return "FieldAccessor.ForNamedField.PreparationHandler.NoOp." + name();
}
}
/**
* A preparation handler that actually defines a field on an instrumented type.
*/
class FieldDefiner implements PreparationHandler {
/**
* The name of the field that is defined by this preparation handler.
*/
private final String name;
/**
* The type of the field that is to be defined.
*/
private final TypeDescription typeDescription;
/**
* The modifier of the field that is to be defined.
*/
private final int modifiers;
/**
* Creates a new field definer.
*
* @param name The name of the field that is defined by this preparation handler.
* @param typeDescription The type of the field that is to be defined.
* @param modifiers The modifiers of the field that is to be defined.
*/
protected FieldDefiner(String name, TypeDescription typeDescription, int modifiers) {
this.name = name;
this.typeDescription = typeDescription;
this.modifiers = modifiers;
}
/**
* Creates a new preparation handler that defines a given field.
*
* @param name The name of the field that is defined by this preparation handler.
* @param typeDescription The type of the field that is to be defined.
* @param contributor The modifiers of the field that is to be defined.
* @return A corresponding preparation handler.
*/
public static PreparationHandler of(String name, TypeDescription typeDescription, ModifierContributor.ForField... contributor) {
return new FieldDefiner(name, typeDescription, resolveModifierContributors(ByteBuddyCommons.FIELD_MODIFIER_MASK, contributor));
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType.withField(name, TargetType.resolve(typeDescription, instrumentedType), modifiers);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
FieldDefiner that = (FieldDefiner) other;
return modifiers == that.modifiers
&& name.equals(that.name)
&& typeDescription.equals(that.typeDescription);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + typeDescription.hashCode();
result = 31 * result + modifiers;
return result;
}
@Override
public String toString() {
return "FieldAccessor.ForNamedField.PreparationHandler.FieldDefiner{" +
"name='" + name + '\'' +
", typeDescription=" + typeDescription +
", modifiers=" + modifiers +
'}';
}
}
}
}
/**
* An byte code appender for an field accessor implementation.
*/
protected class Appender implements ByteCodeAppender {
/**
* The field locator for implementing this appender.
*/
private final FieldLocator fieldLocator;
/**
* Creates a new byte code appender for a field accessor implementation.
*
* @param fieldLocator The field locator for this byte code appender.
*/
protected Appender(FieldLocator fieldLocator) {
this.fieldLocator = fieldLocator;
}
@Override
public Size apply(MethodVisitor methodVisitor,
Implementation.Context implementationContext,
MethodDescription instrumentedMethod) {
if (isConstructor().matches(instrumentedMethod)) {
throw new IllegalArgumentException("Constructors cannot define beans: " + instrumentedMethod);
}
if (takesArguments(0).and(not(returns(void.class))).matches(instrumentedMethod)) {
return applyGetter(methodVisitor,
implementationContext,
fieldLocator.locate(getFieldName(instrumentedMethod)),
instrumentedMethod);
} else if (takesArguments(1).and(returns(void.class)).matches(instrumentedMethod)) {
return applySetter(methodVisitor,
implementationContext,
fieldLocator.locate(getFieldName(instrumentedMethod)),
instrumentedMethod);
} else {
throw new IllegalArgumentException("Method " + implementationContext + " is no bean property");
}
}
/**
* Returns the outer instance.
*
* @return The outer instance.
*/
private FieldAccessor getFieldAccessor() {
return FieldAccessor.this;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& fieldLocator.equals(((Appender) other).fieldLocator)
&& FieldAccessor.this.equals(((Appender) other).getFieldAccessor());
}
@Override
public int hashCode() {
return 31 * FieldAccessor.this.hashCode() + fieldLocator.hashCode();
}
@Override
public String toString() {
return "FieldAccessor.Appender{" +
"fieldLocator=" + fieldLocator +
"fieldAccessor=" + FieldAccessor.this +
'}';
}
}
}