19
19
import groovy .transform .Field ;
20
20
import groovy .transform .Generated ;
21
21
import groovy .transform .Immutable ;
22
- import groovyjarjarasm .asm .Opcodes ;
23
22
import lombok .Getter ;
24
23
import lombok .RequiredArgsConstructor ;
25
24
import lombok .Value ;
@@ -240,21 +239,22 @@ public void visitClass(ClassNode clazz) {
240
239
List <J .Modifier > modifiers = getModifiers ();
241
240
242
241
Space kindPrefix = whitespace ();
243
- J .ClassDeclaration .Kind .Type kindType = null ;
242
+ J .ClassDeclaration .Kind .Type kindType ;
244
243
if (sourceStartsWith ("class" )) {
245
244
kindType = J .ClassDeclaration .Kind .Type .Class ;
246
245
skip ("class" );
247
- } else if (sourceStartsWith ("interface" )) {
248
- kindType = J .ClassDeclaration .Kind .Type .Interface ;
249
- skip ("interface" );
250
- } else if (sourceStartsWith ("@interface" )) {
246
+ } else if (clazz .isAnnotationDefinition ()) {
251
247
kindType = J .ClassDeclaration .Kind .Type .Annotation ;
252
248
skip ("@interface" );
253
- } else if (sourceStartsWith ("enum" )) {
249
+ } else if (clazz .isInterface ()) {
250
+ kindType = J .ClassDeclaration .Kind .Type .Interface ;
251
+ skip ("interface" );
252
+ } else if (clazz .isEnum ()) {
254
253
kindType = J .ClassDeclaration .Kind .Type .Enum ;
255
254
skip ("enum" );
255
+ } else {
256
+ throw new IllegalStateException ("Unexpected class type: " + name ());
256
257
}
257
- assert kindType != null ;
258
258
J .ClassDeclaration .Kind kind = new J .ClassDeclaration .Kind (randomId (), kindPrefix , Markers .EMPTY , emptyList (), kindType );
259
259
J .Identifier name = new J .Identifier (randomId (), whitespace (), Markers .EMPTY , emptyList (), name (), typeMapping .type (clazz ), null );
260
260
JContainer <J .TypeParameter > typeParameterContainer = null ;
@@ -310,6 +310,7 @@ public void visitClass(ClassNode clazz) {
310
310
311
311
J .Block visitClassBlock (ClassNode clazz ) {
312
312
NavigableMap <LineColumn , ASTNode > sortedByPosition = new TreeMap <>();
313
+ List <FieldNode > enumConstants = new ArrayList <>();
313
314
for (MethodNode method : clazz .getMethods ()) {
314
315
// Most synthetic methods do not appear in source code and should be skipped entirely.
315
316
if (method .isSynthetic ()) {
@@ -350,7 +351,11 @@ class A {
350
351
if (!appearsInSource (field )) {
351
352
continue ;
352
353
}
353
- if (field .hasInitialExpression () && field .getInitialExpression () instanceof ConstructorCallExpression ) {
354
+ if (field .isEnum ()) {
355
+ enumConstants .add (field );
356
+ continue ;
357
+ }
358
+ if (field .getInitialExpression () instanceof ConstructorCallExpression ) {
354
359
ConstructorCallExpression cce = (ConstructorCallExpression ) field .getInitialExpression ();
355
360
if (cce .isUsingAnonymousInnerClass () && cce .getType () instanceof InnerClassNode ) {
356
361
fieldInitializers .add ((InnerClassNode ) cce .getType ());
@@ -373,9 +378,35 @@ class A {
373
378
sortedByPosition .put (pos (icn ), icn );
374
379
}
375
380
376
- return new J .Block (randomId (), sourceBefore ("{" ), Markers .EMPTY ,
381
+ Space blockPrefix = sourceBefore ("{" );
382
+
383
+ List <JRightPadded <Statement >> statements = new ArrayList <>();
384
+ if (!enumConstants .isEmpty ()) {
385
+ enumConstants .sort (Comparator .comparing (GroovyParserVisitor ::pos ));
386
+
387
+ List <JRightPadded <J .EnumValue >> enumValues = new ArrayList <>();
388
+ for (int i = 0 ; i < enumConstants .size (); i ++) {
389
+ J .EnumValue enumValue = visitEnumField (enumConstants .get (i ));
390
+ JRightPadded <J .EnumValue > paddedEnumValue = JRightPadded .build (enumValue ).withAfter (whitespace ());
391
+ if (sourceStartsWith ("," )) {
392
+ skip ("," );
393
+ if (i == enumConstants .size () - 1 ) {
394
+ paddedEnumValue = paddedEnumValue .withMarkers (Markers .build (singleton (new TrailingComma (randomId (), whitespace ()))));
395
+ }
396
+ }
397
+ enumValues .add (paddedEnumValue );
398
+ }
399
+
400
+ J .EnumValueSet enumValueSet = new J .EnumValueSet (randomId (), EMPTY , Markers .EMPTY , enumValues , sourceStartsWith (";" ));
401
+ if (enumValueSet .isTerminatedWithSemicolon ()) {
402
+ skip (";" );
403
+ }
404
+ statements .add (JRightPadded .build (enumValueSet ));
405
+ }
406
+
407
+ return new J .Block (randomId (), blockPrefix , Markers .EMPTY ,
377
408
JRightPadded .build (false ),
378
- sortedByPosition .values ().stream ()
409
+ ListUtils . concatAll ( statements , sortedByPosition .values ().stream ()
379
410
// anonymous classes will be visited as part of visiting the ConstructorCallExpression
380
411
.filter (ast -> !(ast instanceof InnerClassNode && ((InnerClassNode ) ast ).isAnonymous ()))
381
412
.map (ast -> {
@@ -391,7 +422,7 @@ class A {
391
422
Statement stat = pollQueue ();
392
423
return maybeSemicolon (stat );
393
424
})
394
- .collect (toList ()),
425
+ .collect (toList ())) ,
395
426
sourceBefore ("}" ));
396
427
}
397
428
@@ -403,17 +434,27 @@ public void visitBlockStatement(BlockStatement statement) {
403
434
404
435
@ Override
405
436
public void visitField (FieldNode field ) {
406
- if ((field .getModifiers () & Opcodes .ACC_ENUM ) != 0 ) {
407
- visitEnumField (field );
408
- } else {
409
- visitVariableField (field );
437
+ if (field .isEnum ()) {
438
+ // Enum constants are handled separately in visitClassBlock, thus should be skipped here
439
+ return ;
410
440
}
441
+ visitVariableField (field );
411
442
}
412
443
413
- private void visitEnumField (@ SuppressWarnings ("unused" ) FieldNode fieldNode ) {
414
- // Requires refactoring visitClass to use a similar pattern as Java11ParserVisitor.
415
- // Currently, each field is visited one at a time, so we cannot construct the EnumValueSet.
416
- throw new UnsupportedOperationException ("enum fields are not implemented." );
444
+ private J .EnumValue visitEnumField (FieldNode field ) {
445
+ Space prefix = whitespace ();
446
+
447
+ List <J .Annotation > annotations = visitAndGetAnnotations (field , this );
448
+
449
+ Space namePrefix = whitespace ();
450
+ String enumName = field .getName ();
451
+ skip (enumName );
452
+
453
+ J .Identifier name = new J .Identifier (randomId (), namePrefix , Markers .EMPTY , emptyList (), enumName , typeMapping .type (field .getType ()), typeMapping .variableType (field ));
454
+
455
+ // TODO initializer (enum constructor invocation)
456
+
457
+ return new J .EnumValue (randomId (), prefix , Markers .EMPTY , annotations , name , null );
417
458
}
418
459
419
460
private void visitVariableField (FieldNode field ) {
@@ -467,6 +508,7 @@ public void visitMethod(MethodNode method) {
467
508
468
509
List <J .Annotation > annotations = visitAndGetAnnotations (method , this );
469
510
List <J .Modifier > modifiers = getModifiers ();
511
+ boolean isConstructorOfEnum = false ;
470
512
boolean isConstructorOfInnerNonStaticClass = false ;
471
513
J .TypeParameters typeParameters = null ;
472
514
if (method .getGenericsTypes () != null ) {
@@ -484,21 +526,35 @@ public void visitMethod(MethodNode method) {
484
526
Space namePrefix = whitespace ();
485
527
String methodName ;
486
528
if (method instanceof ConstructorNode ) {
487
- /*
488
- To support Java syntax for non-static inner classes, the groovy compiler uses an extra parameter with a reference to its parent class under the hood:
489
- class A { class A {
490
- class B { class B {
491
- String s String s
492
- B(String s) { => B(A $p$, String s) {
493
- => new Object().this$0 = $p$
494
- this.s = s => this.s = s
529
+ // To support special constructors well, the groovy compiler adds extra parameters and statements to the constructor under the hood.
530
+ // In our LST, we don't need this internal logic.
531
+ if (method .getDeclaringClass ().isEnum ()) {
532
+ /*
533
+ For enums, there are two extra parameters and wraps the block in a super call:
534
+ enum A { enum A {
535
+ A1 A1
536
+ A(String s) { => A(String __str, int __int, String s) {
537
+ println "ss" => super() { println "ss" }
538
+ } }
495
539
} }
496
- } }
540
+ */
541
+ isConstructorOfEnum = true ;
542
+ } else {
543
+ /*
544
+ For Java syntax for non-static inner classes, there's an extra parameter with a reference to its parent class and two statements (ConstructorCallExpression and BlockStatement):
545
+ class A { class A {
546
+ class B { class B {
547
+ String s String s
548
+ B(String s) { => B(A $p$, String s) {
549
+ => new Object().this$0 = $p$
550
+ this.s = s => this.s = s
551
+ } }
552
+ } }
553
+ }
554
+ See also: https://groovy-lang.org/differences.html#_creating_instances_of_non_static_inner_classes
555
+ */
556
+ isConstructorOfInnerNonStaticClass = method .getDeclaringClass () instanceof InnerClassNode && (method .getDeclaringClass ().getModifiers () & Modifier .STATIC ) == 0 ;
497
557
}
498
- In our LST, we don't need this internal logic, so we'll skip the first param + first two statements (ConstructorCallExpression and BlockStatement)}
499
- See also: https://groovy-lang.org/differences.html#_creating_instances_of_non_static_inner_classes
500
- */
501
- isConstructorOfInnerNonStaticClass = method .getDeclaringClass () instanceof InnerClassNode && (method .getDeclaringClass ().getModifiers () & Modifier .STATIC ) == 0 ;
502
558
methodName = method .getDeclaringClass ().getNameWithoutPackage ().replaceFirst (".*\\ $" , "" );
503
559
} else if (source .startsWith (method .getName (), cursor )) {
504
560
methodName = method .getName ();
@@ -521,14 +577,22 @@ class B { class B {
521
577
Space beforeParen = sourceBefore ("(" );
522
578
List <JRightPadded <Statement >> params = new ArrayList <>(method .getParameters ().length );
523
579
Parameter [] unparsedParams = method .getParameters ();
524
- for (int i = (isConstructorOfInnerNonStaticClass ? 1 : 0 ); i < unparsedParams .length ; i ++) {
580
+ int skipParams = isConstructorOfEnum ? 2 : isConstructorOfInnerNonStaticClass ? 1 : 0 ;
581
+ for (int i = skipParams ; i < unparsedParams .length ; i ++) {
525
582
Parameter param = unparsedParams [i ];
526
583
527
584
List <J .Annotation > paramAnnotations = visitAndGetAnnotations (param , this );
528
585
List <J .Modifier > paramModifiers = getModifiers ();
529
- TypeTree paramType = param .isDynamicTyped () ?
530
- new J .Identifier (randomId (), EMPTY , Markers .EMPTY , emptyList (), "" , JavaType .ShallowClass .build ("java.lang.Object" ), null ) :
531
- visitTypeTree (param .getOriginType ());
586
+ TypeTree paramType ;
587
+ if (param .isDynamicTyped ()) {
588
+ if (sourceStartsWith ("java.lang.Object" )) {
589
+ paramType = new J .Identifier (randomId (), whitespace (), Markers .EMPTY , emptyList (), skip (name ()), JavaType .ShallowClass .build ("java.lang.Object" ), null );
590
+ } else {
591
+ paramType = new J .Identifier (randomId (), EMPTY , Markers .EMPTY , emptyList (), "" , JavaType .ShallowClass .build ("java.lang.Object" ), null );
592
+ }
593
+ } else {
594
+ paramType = visitTypeTree (param .getType ());
595
+ }
532
596
533
597
Space varargs = null ;
534
598
if (paramType instanceof J .ArrayType && sourceStartsWith ("..." )) {
@@ -560,7 +624,7 @@ class B { class B {
560
624
singletonList (paramName ))).withAfter (rightPad ));
561
625
}
562
626
563
- if (unparsedParams .length == 0 || ( isConstructorOfInnerNonStaticClass && unparsedParams . length == 1 ) ) {
627
+ if (unparsedParams .length == skipParams ) {
564
628
params .add (JRightPadded .build (new J .Empty (randomId (), sourceBefore (")" ), Markers .EMPTY )));
565
629
}
566
630
@@ -572,13 +636,23 @@ class B { class B {
572
636
573
637
J .Block body = null ;
574
638
if (method .getCode () != null ) {
575
- ASTNode code = isConstructorOfInnerNonStaticClass ?
576
- new BlockStatement (
577
- ((BlockStatement ) method .getCode ()).getStatements ().subList (2 , ((BlockStatement ) method .getCode ()).getStatements ().size ()),
578
- ((BlockStatement ) method .getCode ()).getVariableScope ()
579
- ) :
580
- method .getCode ();
581
- body = bodyVisitor .visit (code );
639
+ if (isConstructorOfInnerNonStaticClass ) {
640
+ body = bodyVisitor .visit (
641
+ new BlockStatement (
642
+ ((BlockStatement ) method .getCode ()).getStatements ().subList (2 , ((BlockStatement ) method .getCode ()).getStatements ().size ()),
643
+ ((BlockStatement ) method .getCode ()).getVariableScope ()
644
+ )
645
+ );
646
+ } else if (isConstructorOfEnum && ((BlockStatement ) method .getCode ()).getStatements ().size () > 1 ) {
647
+ org .codehaus .groovy .ast .stmt .Statement node = ((BlockStatement ) method .getCode ()).getStatements ().get (1 );
648
+ if (node instanceof BlockStatement ) {
649
+ body = bodyVisitor .visit (node );
650
+ } else {
651
+ body = bodyVisitor .visit (method .getCode ());
652
+ }
653
+ } else {
654
+ body = bodyVisitor .visit (method .getCode ());
655
+ }
582
656
}
583
657
584
658
queue .add (new J .MethodDeclaration (
@@ -1372,7 +1446,7 @@ public void visitConstantExpression(ConstantExpression expression) {
1372
1446
}
1373
1447
jType = JavaType .Primitive .Null ;
1374
1448
} else {
1375
- throw new IllegalStateException ("Unexpected constant type " + type );
1449
+ throw new IllegalStateException ("Unexpected constant type: " + type );
1376
1450
}
1377
1451
1378
1452
// Get the string literal from the source, as numeric literals may have a unary operator, underscores, dots and can be followed by "L", "f", or "d"
@@ -2960,6 +3034,9 @@ private boolean appearsInSource(ASTNode node) {
2960
3034
String [] parts = name .split ("\\ ." );
2961
3035
return sourceStartsWith ("@" + name ) || sourceStartsWith ("@" + parts [parts .length - 1 ]);
2962
3036
}
3037
+ if (node instanceof ConstructorNode && ((ConstructorNode ) node ).getDeclaringClass ().isEnum ()) {
3038
+ return ((ConstructorNode ) node ).getAnnotations (new ClassNode (Generated .class )).isEmpty ();
3039
+ }
2963
3040
2964
3041
return node .getColumnNumber () >= 0 && node .getLineNumber () >= 0 && node .getLastColumnNumber () >= 0 && node .getLastLineNumber () >= 0 ;
2965
3042
}
0 commit comments