Skip to content

Commit bb134dd

Browse files
committed
support for java records by code transformation
1 parent 2d07e52 commit bb134dd

2 files changed

Lines changed: 151 additions & 0 deletions

File tree

key.core/src/main/java/de/uka/ilkd/key/java/transformations/KeYJavaPipeline.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public List<JavaTransformer> getSteps() {
3939
public static KeYJavaPipeline createDefault(TransformationPipelineServices pipelineServices) {
4040
KeYJavaPipeline p = new KeYJavaPipeline(pipelineServices);
4141
p.add(new EnumClassBuilder(pipelineServices));
42+
p.add(new RecordClassBuilder(pipelineServices));
4243
p.add(new JMLTransformer(pipelineServices));
4344
p.add(new JmlDocRemoval(pipelineServices));
4445
p.add(new ImplicitFieldAdder(pipelineServices));
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/* This file is part of KeY - https://key-project.org
2+
* KeY is licensed under the GNU General Public License Version 2
3+
* SPDX-License-Identifier: GPL-2.0-only */
4+
package de.uka.ilkd.key.java.transformations.pipeline;
5+
6+
import com.github.javaparser.StaticJavaParser;
7+
import com.github.javaparser.ast.CompilationUnit;
8+
import com.github.javaparser.ast.NodeList;
9+
import com.github.javaparser.ast.body.*;
10+
import com.github.javaparser.ast.expr.*;
11+
import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName;
12+
import com.github.javaparser.ast.stmt.BlockStmt;
13+
import com.github.javaparser.ast.stmt.ReturnStmt;
14+
import com.github.javaparser.ast.type.Type;
15+
16+
import javax.annotation.processing.Generated;
17+
18+
import java.util.Arrays;
19+
import java.util.List;
20+
21+
import static com.github.javaparser.ast.Modifier.DefaultKeyword.*;
22+
23+
/// This transformation is made to transform any found [RecordDeclaration] into a corresponding
24+
/// [ClassOrInterfaceDeclaration].
25+
///
26+
/// @author weigl
27+
/// @since 2026-03-11
28+
public class RecordClassBuilder extends JavaTransformer {
29+
public RecordClassBuilder(TransformationPipelineServices pipelineServices) {
30+
super(pipelineServices);
31+
}
32+
33+
@Override
34+
public void apply(CompilationUnit cu) {
35+
System.out.println(cu);
36+
cu.walk(RecordDeclaration.class, it -> {
37+
ClassOrInterfaceDeclaration clazz = transform(it);
38+
it.replace(clazz);
39+
});
40+
}
41+
42+
private ClassOrInterfaceDeclaration transform(RecordDeclaration recordDeclaration) {
43+
ClassOrInterfaceDeclaration clazz = new ClassOrInterfaceDeclaration();
44+
clazz.setModifiers(recordDeclaration.getModifiers());
45+
clazz.addModifier(FINAL);
46+
clazz.setName(recordDeclaration.getName());
47+
48+
clazz.addExtendedType(java.lang.Record.class);
49+
50+
clazz.addAnnotation(Generated.class);
51+
52+
for (Parameter parameter : recordDeclaration.getParameters()) {
53+
FieldDeclaration field = clazz.addField(parameter.type(), parameter.getNameAsString(), PRIVATE, FINAL);
54+
field.getModifiers().addAll(parameter.getModifiers());
55+
56+
MethodDeclaration getter = clazz.addMethod(parameter.getNameAsString());
57+
getter.setType(parameter.type());
58+
getter.addModifier(PUBLIC, FINAL);
59+
}
60+
61+
// TODO generate equals and hashcode
62+
boolean hasNoEquals = recordDeclaration.getMethodsBySignature("equals", "java.lang.Object").isEmpty();
63+
boolean hasNoHashcode = recordDeclaration.getMethodsBySignature("hashCode").isEmpty();
64+
65+
if (hasNoEquals) {
66+
MethodDeclaration equals = clazz.addMethod("hashCode", PUBLIC, FINAL);
67+
equals.addAnnotation(Override.class);
68+
equals.setType(Boolean.TYPE);
69+
Type tObject = StaticJavaParser.parseType("java.lang.Object");
70+
equals.getParameters().add(new Parameter(tObject, "o"));
71+
BlockStmt body = equals.getBody().get();
72+
body.addStatement("if(this == other) return true;");
73+
body.addStatement("if(!(o instanceof %s that)) return false;".formatted(clazz.getNameAsString()));
74+
75+
Expression equalFields = recordDeclaration.getParameters().stream()
76+
.map(it -> callObjects("equals", it.getNameAsExpression(),
77+
new FieldAccessExpr(new NameExpr("o"), it.getNameAsString())))
78+
.reduce((a, b) -> new BinaryExpr(a, b, BinaryExpr.Operator.AND))
79+
.orElse(new BooleanLiteralExpr(true));
80+
body.addStatement(new ReturnStmt(equalFields));
81+
82+
body.addStatement("return true");
83+
}
84+
85+
if (hasNoHashcode) {
86+
MethodDeclaration hashCode = clazz.addMethod("hashCode", PUBLIC, FINAL);
87+
hashCode.addAnnotation(Override.class);
88+
hashCode.setType(Integer.TYPE);
89+
List<Expression> args = recordDeclaration.getParameters()
90+
.stream().map(NodeWithSimpleName::getNameAsExpression)
91+
.map(it -> (Expression) it)
92+
.toList();
93+
final Expression call = callObjects("hash", args);
94+
hashCode.getBody().get()
95+
.addStatement(new ReturnStmt(call));
96+
}
97+
98+
// TODO generate to String
99+
boolean hasNoToString = recordDeclaration.getMethodsBySignature("toString").isEmpty();
100+
if (hasNoToString) {
101+
MethodDeclaration toString = clazz.addMethod("toString", PUBLIC, FINAL, JML_NON_NULL);
102+
toString.addAnnotation(Override.class);
103+
toString.setType(String.class);
104+
ConcatBuilder concatBuilder = new ConcatBuilder();
105+
concatBuilder.addStr(clazz.getNameAsString() + "[");
106+
for (Parameter parameter : recordDeclaration.getParameters()) {
107+
concatBuilder.addStr(parameter.getNameAsString() + "=");
108+
concatBuilder.addVar(parameter.getNameAsString());
109+
concatBuilder.addStr(",");
110+
}
111+
concatBuilder.addStr("]");
112+
toString.getBody().get().addStatement(new ReturnStmt(concatBuilder.expr));
113+
}
114+
115+
116+
clazz.getMembers().addAll(recordDeclaration.getMembers());
117+
return clazz;
118+
}
119+
120+
private Expression callObjects(String method, Expression... exprs) {
121+
return callObjects(method, Arrays.stream(exprs).toList());
122+
}
123+
124+
private Expression callObjects(String method, List<Expression> exprs) {
125+
var objects = new FieldAccessExpr(new FieldAccessExpr(new NameExpr("java"), "lang"), "Objects");
126+
return new MethodCallExpr(objects, method, new NodeList<>(exprs));
127+
}
128+
129+
private static final class ConcatBuilder {
130+
public Expression expr = null;
131+
132+
133+
public ConcatBuilder addStr(String s) {
134+
return concat(new StringLiteralExpr(s));
135+
}
136+
137+
private ConcatBuilder concat(com.github.javaparser.ast.expr.Expression expr) {
138+
if (this.expr == null) {
139+
this.expr = expr;
140+
} else {
141+
this.expr = new BinaryExpr(this.expr, expr, BinaryExpr.Operator.PLUS);
142+
}
143+
return this;
144+
}
145+
146+
public ConcatBuilder addVar(String s) {
147+
return concat(new NameExpr(s));
148+
}
149+
}
150+
}

0 commit comments

Comments
 (0)