1 package org.pojomatic.internal;
2
3 import java.lang.invoke.CallSite;
4 import java.lang.invoke.MethodHandles;
5 import java.lang.invoke.MethodType;
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.HashMap;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Objects;
12 import java.util.concurrent.atomic.AtomicLong;
13
14 import org.objectweb.asm.ClassWriter;
15 import org.objectweb.asm.ClassVisitor;
16 import org.objectweb.asm.Handle;
17 import org.objectweb.asm.Label;
18 import org.objectweb.asm.MethodVisitor;
19 import org.objectweb.asm.Type;
20 import org.pojomatic.Pojomator;
21 import org.pojomatic.PropertyElement;
22 import org.pojomatic.annotations.PojoFormat;
23 import org.pojomatic.annotations.SkipArrayCheck;
24 import org.pojomatic.diff.Differences;
25 import org.pojomatic.diff.NoDifferences;
26 import org.pojomatic.diff.PropertyDifferences;
27 import org.pojomatic.diff.ValueDifference;
28 import org.pojomatic.formatter.DefaultEnhancedPojoFormatter;
29 import org.pojomatic.formatter.EnhancedPojoFormatter;
30 import org.pojomatic.formatter.EnhancedPropertyFormatter;
31
32 import static org.objectweb.asm.Opcodes.*;
33
34 class PojomatorByteCodeGenerator {
35 @Deprecated
36 private static final String ENHANCED_POJO_FORMATTER_WRAPPER_INTERNAL_NAME =
37 internalName(org.pojomatic.internal.EnhancedPojoFormatterWrapper.class);
38 private static final Object[] NO_STACK = new Object[] {};
39 private static final String OBJECT_INTERNAL_NAME = internalName(Object.class);
40 private static final String BASE_POJOMATOR_INTERNAL_NAME = internalName(BasePojomator.class);
41 static final String POJO_CLASS_FIELD_NAME = "pojoClass";
42 private static final String BOOTSTRAP_METHOD_NAME = "bootstrap";
43
44 private static final AtomicLong counter = new AtomicLong();
45
46 final String pojomatorClassName;
47 private final String pojomatorInternalClassName;
48 private final String pojomatorInternalClassDesc;
49 private final Class<?> pojoClass;
50 private final String pojoDescriptor;
51 private final ClassProperties classProperties;
52 private final Handle bootstrapMethod;
53 private final Map<PropertyElement, Integer> propertyNumbers = new HashMap<>();
54
55 private MethodVisitor mv;
56
57
58
59
60 private static class StackAdjustments {
61
62
63
64 boolean wideProperty;
65
66 int adjustments(int widePropertyWeight) {
67 return (wideProperty ? widePropertyWeight : 0);
68 }
69 }
70
71 PojomatorByteCodeGenerator(Class<?> pojoClass, ClassProperties classProperties) {
72 this.pojomatorClassName = PojomatorStub.class.getName() + "$" + counter.incrementAndGet();
73 this.pojomatorInternalClassName = internalName(pojomatorClassName);
74 this.pojomatorInternalClassDesc = "L" + pojomatorInternalClassName + ";";
75 this.pojoClass = pojoClass;
76 this.pojoDescriptor = classDesc(pojoClass);
77 this.classProperties = classProperties;
78 this.bootstrapMethod = new Handle(
79 H_INVOKESTATIC,
80 BASE_POJOMATOR_INTERNAL_NAME,
81 BOOTSTRAP_METHOD_NAME,
82 methodDesc(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Class.class), false);
83 int propertyNumber = 1;
84 for (PropertyElement property: classProperties.getAllProperties()) {
85 propertyNumbers.put(property, propertyNumber++);
86 }
87 }
88
89 byte[] makeClassBytes() {
90 ClassWriter classWriter = new ClassWriter(0);
91
92 acceptClassVisitor(classWriter);
93 return classWriter.toByteArray();
94 }
95
96 private void acceptClassVisitor(ClassVisitor classWriter) {
97 classWriter.visit(V1_7, ACC_PUBLIC + ACC_SUPER + ACC_SYNTHETIC, pojomatorInternalClassName, null,
98 BASE_POJOMATOR_INTERNAL_NAME, new String[] { internalName(Pojomator.class) });
99
100 classWriter.visitSource("Look for visitLineNumber", null);
101
102 makeFields(classWriter);
103
104 makeConstructor(classWriter);
105
106 for (PropertyElement propertyElement: classProperties.getAllProperties()) {
107 makeAccessor(classWriter, propertyElement);
108 }
109
110 makeDoEquals(classWriter);
111 makeDoHashCode(classWriter);
112 makeDoToString(classWriter);
113 makeDoDiff(classWriter);
114
115 classWriter.visitEnd();
116 }
117
118 private void makeFields(ClassVisitor classVisitor) {
119
120 for (PropertyElement property: classProperties.getToStringProperties()) {
121 visitField(
122 classVisitor, ACC_STATIC, propertyFormatterName(property), classDesc(EnhancedPropertyFormatter.class));
123 }
124 for (PropertyElement property: classProperties.getAllProperties()) {
125 visitField(
126 classVisitor, ACC_STATIC, propertyElementName(property), classDesc(PropertyElement.class));
127 }
128 }
129
130 private static void visitField(ClassVisitor classVisitor, int flags, String name, String classDescriptor) {
131 classVisitor.visitField(flags, name, classDescriptor, null, null).visitEnd();
132 }
133
134
135
136
137
138
139
140 private void makeAccessor(ClassVisitor classWriter, PropertyElement propertyElement) {
141 LocalVariable pojo = new LocalVariable("pojo", Object.class, null, 0);
142 int maxStackSize = 1;
143 String accessorName = propertyAccessorName(propertyElement);
144 mv = classWriter.visitMethod(
145 ACC_PRIVATE | ACC_STATIC, accessorName, accessorMethodDescription(propertyElement), null, null);
146 mv.visitCode();
147 Label start = visitNewLabel();
148 pojo.acceptLoad(mv);
149 visitLineNumber(4, propertyElement);
150 mv.visitInvokeDynamicInsn(
151 accessorName, accessorMethodDescription(propertyElement), bootstrapMethod, Type.getType(pojomatorInternalClassDesc));
152 visitLineNumber(5, propertyElement);
153
154
155 Class<?> propertyType = propertyElement.getPropertyType();
156 if (propertyType.isPrimitive()) {
157 if (propertyType == float.class) {
158 mv.visitInsn(FRETURN);
159 }
160 else if (propertyType == long.class) {
161 maxStackSize++;
162 mv.visitInsn(LRETURN);
163 }
164 else if (propertyType == double.class) {
165 maxStackSize++;
166 mv.visitInsn(DRETURN);
167 }
168 else {
169 mv.visitInsn(IRETURN);
170 }
171 }
172 else {
173 mv.visitInsn(ARETURN);
174 }
175
176 Label end = visitNewLabel();
177 pojo.withScope(start, end).acceptLocalVariable(mv);
178 mv.visitMaxs(maxStackSize, 2);
179 mv.visitEnd();
180 }
181
182 private void makeConstructor(ClassVisitor cw) {
183 LocalVariable varThis = new LocalVariable("this", pojomatorInternalClassDesc, null, 0);
184 LocalVariable varPojoClass = new LocalVariable(POJO_CLASS_FIELD_NAME, Class.class, null, 1);
185 LocalVariable varClassProperties = new LocalVariable("classProperties", ClassProperties.class, null, 2);
186 mv = cw.visitMethod(ACC_PUBLIC, "<init>", methodDesc(void.class, Class.class, ClassProperties.class), null, null);
187 mv.visitCode();
188 Label start = visitNewLabel();
189 varThis.acceptLoad(mv);
190 varPojoClass.acceptLoad(mv);
191 varClassProperties.acceptLoad(mv);
192 visitLineNumber(6, null);
193 construct(BasePojomator.class, Class.class, ClassProperties.class);
194 mv.visitInsn(RETURN);
195 Label end = visitNewLabel();
196 varThis.withScope(start, end).acceptLocalVariable(mv);
197 varPojoClass.withScope(start, end).acceptLocalVariable(mv);
198 varClassProperties.withScope(start, end).acceptLocalVariable(mv);
199 mv.visitMaxs(3, 3);
200 mv.visitEnd();
201 }
202
203
204
205
206
207 private void makeDoEquals(ClassVisitor cw) {
208 LocalVariable varThis = new LocalVariable("this", pojomatorInternalClassDesc, null, 0);
209 LocalVariable varPojo1 = new LocalVariable("pojo1", pojoClass, pojoDescriptor, 1);
210 LocalVariable varPojo2 = new LocalVariable("pojo2", pojoClass, pojoDescriptor, 2);
211
212 StackAdjustments stackAdjustments = new StackAdjustments();
213
214 Object[] localVars = new Object[] {pojomatorInternalClassName, OBJECT_INTERNAL_NAME, OBJECT_INTERNAL_NAME};
215
216 mv = cw.visitMethod(ACC_PUBLIC, "doEquals", methodDesc(boolean.class, Object.class, Object.class), null, null);
217
218
219 Label returnFalse = new Label();
220
221 Label compatibleTypes = new Label();
222
223 mv.visitCode();
224 Label start = visitNewLabel();
225 varPojo1.acceptLoad(mv);
226 visitLineNumber(7, null);
227 checkNotNull();
228 varPojo2.acceptLoad(mv);
229 visitLineNumber(8, null);
230 Label notSameInstance = new Label();
231 mv.visitJumpInsn(IF_ACMPNE, notSameInstance);
232
233
234 mv.visitInsn(ICONST_1);
235 mv.visitInsn(IRETURN);
236
237 mv.visitLabel(notSameInstance);
238
239
240 mv.visitFrame(F_FULL, 3, localVars, 0, NO_STACK);
241 visitLineNumber(9, null);
242 varPojo2.acceptLoad(mv);
243 mv.visitJumpInsn(IFNULL, returnFalse);
244
245
246 varThis.acceptLoad(mv);
247 visitLineNumber(10, null);
248 invokeVirtual(Object.class, "getClass", Class.class);
249 varPojo1.acceptLoad(mv);
250 visitLineNumber(11, null);
251 invokeVirtual(Object.class, "getClass", Class.class);
252 mv.visitJumpInsn(IF_ACMPEQ, compatibleTypes);
253
254
255 varThis.acceptLoad(mv);
256 varPojo2.acceptLoad(mv);
257 visitLineNumber(12, null);
258 invokeVirtual(Object.class, "getClass", Class.class);
259 visitLineNumber(13, null);
260 invokeVirtual(BasePojomator.class, "isCompatibleForEquality", boolean.class, Class.class);
261 mv.visitJumpInsn(IFEQ, returnFalse);
262
263
264 mv.visitLabel(compatibleTypes);
265 mv.visitFrame(F_FULL, 3, localVars, 0, NO_STACK);
266
267
268 for(PropertyElement propertyElement: classProperties.getEqualsProperties()) {
269 visitLineNumber(14, propertyElement);
270 visitAccessorAndConvert(varPojo1, propertyElement);
271 visitLineNumber(15, propertyElement);
272 visitAccessorAndConvert(varPojo2, propertyElement);
273 visitLineNumber(16, propertyElement);
274 compareProperties(mv, returnFalse, propertyElement, stackAdjustments);
275 }
276
277 mv.visitInsn(ICONST_1);
278 mv.visitInsn(IRETURN);
279
280 mv.visitLabel(returnFalse);
281 mv.visitFrame(F_FULL, 3, localVars, 0, NO_STACK);
282 mv.visitInsn(ICONST_0);
283 mv.visitInsn(IRETURN);
284
285 Label end = visitNewLabel();
286 varThis.withScope(start, end).acceptLocalVariable(mv);
287 varPojo1.withScope(start, end).acceptLocalVariable(mv);
288 varPojo2.withScope(start, end).acceptLocalVariable(mv);
289 mv.visitMaxs(2 + stackAdjustments.adjustments(2), 3);
290 mv.visitEnd();
291 }
292
293
294
295
296
297
298
299
300
301
302 private void compareProperties(
303 MethodVisitor mv, Label notEqualLabel, PropertyElement propertyElement, StackAdjustments stackAdjustments) {
304 Class<?> propertyType = propertyElement.getPropertyType();
305 if (propertyType.isPrimitive()) {
306 if (isWide(propertyElement)) {
307 stackAdjustments.wideProperty = true;
308 mv.visitInsn(LCMP);
309 mv.visitJumpInsn(IFNE, notEqualLabel);
310 }
311 else {
312 mv.visitJumpInsn(IF_ICMPNE, notEqualLabel);
313 }
314 }
315 else {
316 if(propertyType.isArray()) {
317 Class<?> componentType = propertyType.getComponentType();
318 if (componentType.isPrimitive()) {
319 visitLineNumber(17, propertyElement);
320 invokeStatic(Arrays.class, "equals",boolean.class, propertyType, propertyType);
321 }
322 else {
323 visitLineNumber(18, propertyElement);
324 invokeStatic(BasePojomator.class, "compareArrays", boolean.class, Object.class, Object.class);
325 }
326 }
327 else {
328 if (isObjectPossiblyHoldingArray(propertyElement)) {
329 visitLineNumber(19, propertyElement);
330 invokeStatic(BasePojomator.class, "areObjectValuesEqual", boolean.class, Object.class, Object.class);
331 }
332 else {
333 visitLineNumber(20, propertyElement);
334 invokeStatic(Objects.class, "equals", boolean.class, Object.class, Object.class);
335 }
336 }
337 mv.visitJumpInsn(IFEQ, notEqualLabel);
338 }
339 }
340
341
342
343
344
345 private void makeDoHashCode(ClassVisitor cw) {
346 LocalVariable varThis = new LocalVariable("this", pojomatorInternalClassDesc, null, 0);
347 LocalVariable varPojo = new LocalVariable("pojo", pojoClass, pojoDescriptor, 1);
348
349 int longOrDoubleStackAdjustment = 0;
350 Object[] localVars = new Object[] {pojomatorInternalClassName, OBJECT_INTERNAL_NAME};
351
352 mv = cw.visitMethod(ACC_PUBLIC, "doHashCode", methodDesc(int.class, Object.class), null, null);
353 mv.visitCode();
354 Label start = visitNewLabel();
355 visitLineNumber(21, null);
356 varPojo.acceptLoad(mv);
357 visitLineNumber(22, null);
358 checkNotNullPop();
359
360
361
362
363 mv.visitInsn(ICONST_1);
364
365 for(PropertyElement propertyElement: classProperties.getHashCodeProperties()) {
366
367 visitLineNumber(23, propertyElement);
368 mv.visitIntInsn(BIPUSH, 31);
369 visitLineNumber(24, propertyElement);
370 mv.visitInsn(IMUL);
371
372 visitLineNumber(25, propertyElement);
373 visitAccessorAndConvert(varPojo, propertyElement);
374 Class<?> propertyType = propertyElement.getPropertyType();
375 if (propertyType.isPrimitive()) {
376
377 switch (propertyType.getName()) {
378 case "boolean":
379 visitLineNumber(26, propertyElement);
380 Label ifeq = new Label();
381 mv.visitJumpInsn(IFEQ, ifeq);
382 mv.visitIntInsn(SIPUSH, Boolean.TRUE.hashCode());
383 Label hashCodeDetermined = new Label();
384 mv.visitJumpInsn(GOTO, hashCodeDetermined);
385 mv.visitLabel(ifeq);
386 mv.visitFrame(F_FULL, 2, localVars, 1, new Object[] {INTEGER});
387 mv.visitIntInsn(SIPUSH, Boolean.FALSE.hashCode());
388 mv.visitLabel(hashCodeDetermined);
389 mv.visitFrame(F_FULL, 2, localVars, 2, new Object[] {INTEGER, INTEGER});
390 break;
391 case "byte":
392 case "char":
393 case "int":
394 case "short":
395 case "float":
396 break;
397 case "double":
398 case "long":
399 longOrDoubleStackAdjustment = 3;
400
401
402
403 visitLineNumber(27, propertyElement);
404
405 mv.visitInsn(DUP2);
406
407 mv.visitIntInsn(BIPUSH, 32);
408 mv.visitInsn(LUSHR);
409
410 mv.visitInsn(LXOR);
411
412 mv.visitInsn(L2I);
413 break;
414 default:
415 throw new IllegalStateException("unknown primitive type " + propertyType.getName());
416 }
417 }
418 else {
419 Label ifNonNull = new Label();
420 Label hashCodeDetermined = new Label();
421
422 mv.visitInsn(DUP);
423 mv.visitJumpInsn(IFNONNULL, ifNonNull);
424
425 mv.visitInsn(POP);
426 mv.visitInsn(ICONST_0);
427 mv.visitJumpInsn(GOTO, hashCodeDetermined);
428
429
430 mv.visitLabel(ifNonNull);
431 mv.visitFrame(
432 F_FULL, 2, localVars, 2, new Object[] {INTEGER, Type.getInternalName(effectiveType(propertyType))});
433
434 if(propertyType.isArray()) {
435 visitLineNumber(28, propertyElement);
436
437 invokeStatic(
438 Arrays.class,
439 isDeepArray(propertyType) ? "deepHashCode" : "hashCode",
440 int.class,
441 propertyType.getComponentType().isPrimitive() ? propertyType : Object[].class);
442 }
443 else if (isObjectPossiblyHoldingArray(propertyElement)) {
444
445
446 mv.visitInsn(DUP);
447 invokeVirtual(Object.class, "getClass", Class.class);
448 visitLineNumber(29, propertyElement);
449 invokeVirtual(Class.class, "isArray", boolean.class);
450 Label isArray = new Label();
451 mv.visitJumpInsn(IFNE, isArray);
452
453
454 visitLineNumber(30, propertyElement);
455 invokeVirtual(Object.class, "hashCode", int.class);
456 mv.visitJumpInsn(GOTO, hashCodeDetermined);
457
458
459 mv.visitLabel(isArray);
460 mv.visitFrame(F_FULL, 2, localVars, 2, new Object[] { INTEGER, Type.getInternalName(propertyType) });
461
462 mv.visitInsn(ICONST_1);
463 visitLineNumber(31, propertyElement);
464 invokeStatic(BasePojomator.class, "arrayHashCode", int.class, Object.class, boolean.class);
465 }
466 else {
467 visitLineNumber(32, propertyElement);
468 invokeVirtual(Object.class, "hashCode", int.class);
469 }
470
471 mv.visitLabel(hashCodeDetermined);
472 mv.visitFrame(F_FULL, 2, localVars, 2, new Object[] {INTEGER, INTEGER});
473 }
474
475 mv.visitInsn(IADD);
476 }
477 mv.visitInsn(IRETURN);
478 Label end = visitNewLabel();
479 varThis.withScope(start, end).acceptLocalVariable(mv);
480 varPojo.withScope(start, end).acceptLocalVariable(mv);
481 mv.visitMaxs(3 + longOrDoubleStackAdjustment, 2);
482 mv.visitEnd();
483 }
484
485
486
487
488
489 private void makeDoToString(ClassVisitor cw) {
490 int longOrDoubleStackAdjustment = 1;
491 LocalVariable varThis = new LocalVariable("this", pojomatorInternalClassDesc, null, 0);
492 LocalVariable varPojo = new LocalVariable("pojo", pojoClass, null, 1);
493 LocalVariable varPojoFormatter=
494 new LocalVariable("pojoFormattor", classDesc(EnhancedPojoFormatter.class), null, 2);
495 LocalVariable varBuilder= new LocalVariable("builder", classDesc(String.class), null, 3);
496
497 mv = cw.visitMethod(ACC_PUBLIC, "doToString", methodDesc(String.class, Object.class), null, null);
498 mv.visitCode();
499 Label start = visitNewLabel();
500 varPojo.acceptLoad(mv);
501 checkNotNullPop();
502
503 constructEnhancedPojoFormatter();
504 varPojoFormatter.acceptStore(mv);
505
506 visitLineNumber(33, null);
507 mv.visitTypeInsn(NEW, internalName(StringBuilder.class));
508 mv.visitInsn(DUP);
509 construct(StringBuilder.class);
510 varBuilder.acceptStore(mv);
511
512 varPojoFormatter.acceptLoad(mv);
513 varBuilder.acceptLoad(mv);
514 loadPojoClass(varThis);
515
516 visitLineNumber(34, null);
517
518 invokeInterface(EnhancedPojoFormatter.class, "appendToStringPrefix", void.class, StringBuilder.class, Class.class);
519
520 for(PropertyElement propertyElement: classProperties.getToStringProperties()) {
521 if (isWide(propertyElement)) {
522 longOrDoubleStackAdjustment = 1;
523 }
524
525
526 varPojoFormatter.acceptLoad(mv);
527 varBuilder.acceptLoad(mv);
528 visitLineNumber(35, propertyElement);
529 loadPropertyElementField(propertyElement);
530 visitLineNumber(36, propertyElement);
531 invokeInterface(
532 EnhancedPojoFormatter.class, "appendPropertyPrefix", void.class, StringBuilder.class, PropertyElement.class);
533
534
535 visitLineNumber(37, propertyElement);
536 mv.visitFieldInsn(
537 GETSTATIC,
538 pojomatorInternalClassName,
539 propertyFormatterName(propertyElement),
540 classDesc(EnhancedPropertyFormatter.class));
541
542
543 varBuilder.acceptLoad(mv);
544 visitLineNumber(38, propertyElement);
545 visitAccessor(varPojo, propertyElement);
546 Class<?> appendType = appendFormattedType(propertyElement.getPropertyType());
547 if (isObjectPossiblyHoldingArray(propertyElement)) {
548 visitLineNumber(39, propertyElement);
549 invokeInterface(
550 EnhancedPropertyFormatter.class, "appendFormattedPossibleArray", void.class, StringBuilder.class, appendType);
551 }
552 else {
553 visitLineNumber(40, propertyElement);
554 invokeInterface(
555 EnhancedPropertyFormatter.class, "appendFormatted", void.class, StringBuilder.class, appendType);
556 }
557
558
559 varPojoFormatter.acceptLoad(mv);
560 varBuilder.acceptLoad(mv);
561 visitLineNumber(41, propertyElement);
562 loadPropertyElementField(propertyElement);
563 visitLineNumber(42, propertyElement);
564 invokeInterface(
565 EnhancedPojoFormatter.class, "appendPropertySuffix", void.class, StringBuilder.class, PropertyElement.class);
566 }
567
568
569 varPojoFormatter.acceptLoad(mv);
570 varBuilder.acceptLoad(mv);
571 loadPojoClass(varThis);
572 visitLineNumber(43, null);
573 invokeInterface(EnhancedPojoFormatter.class, "appendToStringSuffix", void.class, StringBuilder.class, Class.class);
574
575
576 varBuilder.acceptLoad(mv);
577 visitLineNumber(44, null);
578 invokeVirtual(StringBuilder.class, "toString", String.class);
579 mv.visitInsn(ARETURN);
580
581 Label end = visitNewLabel();
582 varThis.withScope(start, end).acceptLocalVariable(mv);
583 varPojo.withScope(start, end).acceptLocalVariable(mv);
584 varPojoFormatter.withScope(start, end).acceptLocalVariable(mv);
585 varBuilder.withScope(start, end).acceptLocalVariable(mv);
586 mv.visitMaxs(3 + longOrDoubleStackAdjustment, 4);
587 mv.visitEnd();
588 }
589
590 private static Class<?> appendFormattedType(Class<?> propertyType) {
591 if (propertyType.isPrimitive()) {
592 return propertyType;
593 }
594 else if (propertyType.isArray()) {
595 return propertyType.getComponentType().isPrimitive() ? propertyType : Object[].class;
596 }
597 else {
598 return Object.class;
599 }
600 }
601
602 private void loadPropertyElementField(PropertyElement propertyElement) {
603 mv.visitFieldInsn(
604 GETSTATIC,
605 pojomatorInternalClassName,
606 propertyElementName(propertyElement),
607 classDesc(PropertyElement.class));
608 }
609
610
611
612
613
614 @SuppressWarnings("deprecation")
615 private void constructEnhancedPojoFormatter() {
616 PojoFormat format = pojoClass.getAnnotation(PojoFormat.class);
617 if (format == null) {
618 mv.visitTypeInsn(NEW, internalName(DefaultEnhancedPojoFormatter.class));
619 mv.visitInsn(DUP);
620 visitLineNumber(45, null);
621 construct(DefaultEnhancedPojoFormatter.class);
622 }
623 else {
624 Class<? extends org.pojomatic.formatter.PojoFormatter> pojoFormatterClass = format.value();
625
626
627 boolean isEnhancedFormatter = EnhancedPojoFormatter.class.isAssignableFrom(pojoFormatterClass);
628 if (! isEnhancedFormatter) {
629 visitLineNumber(46, null);
630 mv.visitTypeInsn(NEW, ENHANCED_POJO_FORMATTER_WRAPPER_INTERNAL_NAME);
631 mv.visitInsn(DUP);
632 }
633 mv.visitTypeInsn(NEW, internalName(pojoFormatterClass));
634 mv.visitInsn(DUP);
635 visitLineNumber(47, null);
636 construct(pojoFormatterClass);
637 if (! isEnhancedFormatter) {
638 visitLineNumber(48, null);
639 construct(
640 org.pojomatic.internal.EnhancedPojoFormatterWrapper.class, org.pojomatic.formatter.PojoFormatter.class);
641 }
642 }
643 }
644
645
646
647
648
649
650 private void loadPojoClass(LocalVariable varThis) {
651 visitLineNumber(49, null);
652 varThis.acceptLoad(mv);
653 mv.visitFieldInsn(GETFIELD, BASE_POJOMATOR_INTERNAL_NAME, POJO_CLASS_FIELD_NAME, classDesc(Class.class));
654
655 }
656
657
658
659
660
661 private void makeDoDiff(ClassVisitor cw) {
662 LocalVariable varThis = new LocalVariable("this", pojomatorInternalClassDesc, null, 0);
663 LocalVariable varPojo1 = new LocalVariable("instance", pojoClass, pojoDescriptor, 1);
664 LocalVariable varPojo2 = new LocalVariable("other", pojoClass, pojoDescriptor, 2);
665 LocalVariable varDifferencesList = new LocalVariable(
666 "differences", List.class, "Ljava/util/List<Lorg/pojomatic/diff/Difference;>;", 3);
667
668 StackAdjustments stackAdjustments = new StackAdjustments();
669 Object[] localVarTypes = new Object[] {
670 pojomatorInternalClassName, OBJECT_INTERNAL_NAME, OBJECT_INTERNAL_NAME, internalName(List.class), null, null };
671
672 mv = cw.visitMethod(ACC_PUBLIC, "doDiff", methodDesc(Differences.class, Object.class, Object.class), null, null);
673 mv.visitCode();
674 Label start = visitNewLabel();
675 varPojo1.acceptLoad(mv);
676 visitLineNumber(50, null);
677 checkNotNull("instance is null");
678 varPojo2.acceptLoad(mv);
679 checkNotNull("other is null");
680
681
682 Label notSameInstance = new Label();
683 mv.visitJumpInsn(IF_ACMPNE, notSameInstance);
684 invokeStatic(NoDifferences.class, "getInstance", NoDifferences.class);
685 mv.visitInsn(ARETURN);
686
687
688 mv.visitLabel(notSameInstance);
689 mv.visitFrame(F_FULL, 3, localVarTypes, 0, NO_STACK);
690 visitLineNumber(51, null);
691 checkCompatibleForEquality(varThis, varPojo1, "instance");
692 visitLineNumber(52, null);
693 checkCompatibleForEquality(varThis, varPojo2, "other");
694
695 Label makeDiferences = notSameInstance;
696 mv.visitTypeInsn(NEW, "java/util/ArrayList");
697 mv.visitInsn(DUP);
698 construct(ArrayList.class);
699 varDifferencesList.acceptStore(mv);
700
701 List<LocalVariable> propertyVariables = new ArrayList<>();
702
703 for(PropertyElement propertyElement: classProperties.getHashCodeProperties()) {
704 int width = isWide(propertyElement) ? 2 : 1;
705 Class<?> propertyType = propertyElement.getPropertyType();
706 LocalVariable varProp1 = new LocalVariable(
707 "property_" + propertyElement.getName() + "_1", propertyType, null, 4);
708
709 LocalVariable varProp2 = new LocalVariable(
710 "property_" + propertyElement.getName() + "_2", propertyType, null, 4 + width);
711 propertyVariables.add(varProp1);
712 propertyVariables.add(varProp2);
713
714 Label blockStart = visitNewLabel();
715
716 visitLineNumber(53, propertyElement);
717 visitAccessor(varPojo1, propertyElement);
718 varProp1.acceptStore(mv);
719 visitLineNumber(54, propertyElement);
720 visitAccessor(varPojo2, propertyElement);
721 varProp2.acceptStore(mv);
722
723 visitLineNumber(55, propertyElement);
724 visitAccessorAndConvert(varPojo1, propertyElement);
725 visitLineNumber(56, propertyElement);
726 visitAccessorAndConvert(varPojo2, propertyElement);
727
728 Label propertiesNotEqual = new Label();
729 Label next = new Label();
730 visitLineNumber(57, propertyElement);
731 compareProperties(mv, propertiesNotEqual, propertyElement, stackAdjustments);
732 mv.visitJumpInsn(GOTO, next);
733
734 mv.visitLabel(propertiesNotEqual);
735
736 localVarTypes[5] = localVarTypes[4] = propertyType.isPrimitive()
737 ? Primitives.getOpcode(propertyType)
738 : internalName(effectiveType(propertyType));
739 mv.visitFrame(F_FULL, 6, localVarTypes, 0, NO_STACK);
740
741
742 varDifferencesList.acceptLoad(mv);
743 mv.visitTypeInsn(NEW, "org/pojomatic/diff/ValueDifference");
744 mv.visitInsn(DUP);
745 mv.visitLdcInsn(propertyElement.getName());
746 varProp1.acceptLoad(mv);
747 visitLineNumber(58, propertyElement);
748 convertToObject(propertyType);
749 varProp2.acceptLoad(mv);
750 visitLineNumber(59, propertyElement);
751 convertToObject(propertyType);
752 visitLineNumber(60, propertyElement);
753 construct(ValueDifference.class, String.class, Object.class, Object.class);
754
755
756 visitLineNumber(61, propertyElement);
757 invokeInterface(List.class, "add", boolean.class, Object.class);
758 mv.visitInsn(POP);
759 mv.visitLabel(next);
760 mv.visitFrame(F_FULL, 4, localVarTypes, 0, NO_STACK);
761
762 varProp1.withScope(blockStart, next);
763 varProp2.withScope(blockStart, next);
764 }
765
766
767 varDifferencesList.acceptLoad(mv);
768 visitLineNumber(62, null);
769 invokeInterface(List.class, "isEmpty", boolean.class);
770 Label hasDifferences = new Label();
771 mv.visitJumpInsn(IFEQ, hasDifferences);
772 visitLineNumber(63, null);
773 invokeStatic(NoDifferences.class, "getInstance", NoDifferences.class);
774 mv.visitInsn(ARETURN);
775
776
777 mv.visitLabel(hasDifferences);
778 mv.visitFrame(F_FULL, 4, localVarTypes, 0, NO_STACK);
779
780 visitLineNumber(64, null);
781 mv.visitTypeInsn(NEW, internalName(PropertyDifferences.class));
782 mv.visitInsn(DUP);
783 varDifferencesList.acceptLoad(mv);
784 visitLineNumber(65, null);
785 construct(PropertyDifferences.class, List.class);
786 mv.visitInsn(ARETURN);
787
788 Label end = visitNewLabel();
789
790 varThis.withScope(start, end).acceptLocalVariable(mv);
791 varPojo1.withScope(start, end).acceptLocalVariable(mv);
792 varPojo2.withScope(start, end).acceptLocalVariable(mv);
793 varDifferencesList.withScope(makeDiferences, end).acceptLocalVariable(mv);
794 for (LocalVariable var: propertyVariables) {
795 var.acceptLocalVariable(mv);
796 }
797 mv.visitMaxs(6 + stackAdjustments.adjustments(2), 6 + stackAdjustments.adjustments(2));
798 mv.visitEnd();
799 }
800
801
802
803
804
805
806
807 private void checkCompatibleForEquality(LocalVariable varThis, LocalVariable var, String message) {
808 varThis.acceptLoad(mv);
809 var.acceptLoad(mv);
810 mv.visitLdcInsn(message);
811 visitLineNumber(66, null);
812 invokeVirtual(BasePojomator.class, "checkCompatibleForEquality", void.class, Object.class, String.class);
813 }
814
815
816
817
818
819
820
821
822
823
824
825
826
827 private boolean isDeepArray(Class<?> propertyType) {
828 return propertyType.equals(Object[].class) || propertyType.getComponentType().isArray();
829 }
830
831
832
833
834
835
836
837 private boolean isObjectPossiblyHoldingArray(PropertyElement propertyElement) {
838 return Object.class.equals(propertyElement.getPropertyType())
839 && ! propertyElement.getElement().isAnnotationPresent(SkipArrayCheck.class);
840 }
841
842
843
844
845
846
847 private void convertToObject(Class<?> propertyType) {
848 if (propertyType.isPrimitive()) {
849 Class<?> wrapperClass = Primitives.getWrapperClass(propertyType);
850 invokeStatic(wrapperClass, "valueOf", wrapperClass, propertyType);
851 }
852 }
853
854
855
856
857
858
859 private void visitAccessorAndConvert(LocalVariable var, PropertyElement propertyElement) {
860 visitAccessor(var, propertyElement);
861 if (propertyElement.getPropertyType().equals(float.class)) {
862 invokeStatic(Float.class, "floatToIntBits", int.class, float.class);
863 }
864 else if (propertyElement.getPropertyType().equals(double.class)) {
865 invokeStatic(Double.class, "doubleToLongBits", long.class, double.class);
866 }
867 }
868
869
870
871
872
873
874 private void visitAccessor(LocalVariable var, PropertyElement propertyElement) {
875 var.acceptLoad(mv);
876 mv.visitMethodInsn(
877 INVOKESTATIC, pojomatorInternalClassName, propertyAccessorName(propertyElement),
878 accessorMethodDescription(propertyElement), false);
879 }
880
881 private String accessorMethodDescription(PropertyElement propertyElement) {
882 return methodDesc(effectiveType(propertyElement.getPropertyType()), Object.class);
883 }
884
885
886
887
888
889
890
891
892 private Class<?> effectiveType(Class<?> propertyClass) {
893 if (propertyClass.isArray()) {
894 return propertyClass.getComponentType().isPrimitive() ? propertyClass : Object[].class;
895 }
896 else {
897 return propertyClass.isPrimitive() ? propertyClass : Object.class;
898 }
899 }
900
901
902
903
904
905 private Label visitNewLabel() {
906 Label label = new Label();
907 mv.visitLabel(label);
908 return label;
909 }
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924 private void visitLineNumber(int lineNumberBase, PropertyElement propertyElement) {
925 Integer offset = propertyNumbers.get(propertyElement);
926 mv.visitLineNumber(lineNumberBase + 100 * (offset == null ? 0 : offset), visitNewLabel());
927 }
928
929
930
931
932
933
934 private static boolean isWide(PropertyElement propertyElement) {
935 Class<?> type = propertyElement.getPropertyType();
936 return type == long.class || type == double.class;
937 }
938
939
940
941
942 private void checkNotNullPop() {
943 invokeStatic(BasePojomator.class, "checkNotNullPop", void.class, Object.class);
944 }
945
946
947
948
949 private void checkNotNull() {
950 invokeStatic(BasePojomator.class, "checkNotNull", Object.class, Object.class);
951 }
952
953
954
955
956
957 private void checkNotNull(String message) {
958 mv.visitLdcInsn(message);
959 invokeStatic(BasePojomator.class, "checkNotNull", Object.class, Object.class, String.class);
960 }
961
962 private void invokeStatic(Class<?> ownerClass, String methodName, Class<?> returnType, Class<?>... parameterTypes) {
963 mv.visitMethodInsn(
964 INVOKESTATIC, internalName(ownerClass), methodName, methodDesc(returnType, parameterTypes), false);
965 }
966
967 private void invokeInterface(
968 Class<?> ownerClass, String methodName, Class<?> returnType, Class<?>... parameterTypes) {
969 mv.visitMethodInsn(
970 INVOKEINTERFACE, internalName(ownerClass), methodName, methodDesc(returnType, parameterTypes), true);
971 }
972
973 private void invokeVirtual(
974 Class<?> ownerClass, String methodName, Class<?> returnType, Class<?>... parameterTypes) {
975 mv.visitMethodInsn(
976 INVOKEVIRTUAL, internalName(ownerClass), methodName, methodDesc(returnType, parameterTypes), false);
977 }
978
979 private void construct(
980 Class<?> ownerClass, Class<?>... parameterTypes) {
981 mv.visitMethodInsn(
982 INVOKESPECIAL, internalName(ownerClass), "<init>", methodDesc(void.class, parameterTypes), false);
983 }
984
985 private static String internalName(Class<?> clazz) {
986 return internalName(clazz.getName());
987 }
988
989 private static String internalName(String className) {
990 return className.replace('.', '/');
991 }
992
993 private static String classDesc(Class<?> clazz) {
994 return Type.getDescriptor(clazz);
995 }
996
997 private static String methodDesc(Class<?> returnType, Class<?>... parameterTypes) {
998 return MethodType.methodType(returnType, parameterTypes).toMethodDescriptorString();
999 }
1000
1001 private static String propertyAccessorName(PropertyElement property) {
1002 return "get_" + qualifiedPropertyName(property);
1003 }
1004
1005 static String propertyElementName(PropertyElement property) {
1006 return "element_" + qualifiedPropertyName(property);
1007 }
1008
1009 static String propertyFormatterName(PropertyElement property) {
1010 return "formatter_" + qualifiedPropertyName(property);
1011 }
1012
1013 private static String qualifiedPropertyName(PropertyElement property) {
1014 return property.getType()
1015 + "_" + property.getDeclaringClass().getName().replace('.', '$')
1016 + "_" + property.getElementName();
1017 }
1018 }