Skip to content

Commit 90478a4

Browse files
committed
Dev: add support of @swiftblock
1 parent 2094697 commit 90478a4

File tree

6 files changed

+320
-4
lines changed

6 files changed

+320
-4
lines changed

compiler/src/main/java/com/readdle/codegen/JavaSwiftProcessor.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.readdle.codegen;
22

3+
import com.readdle.codegen.anotation.SwiftBlock;
34
import com.readdle.codegen.anotation.SwiftDelegate;
45
import com.readdle.codegen.anotation.SwiftReference;
56
import com.readdle.codegen.anotation.SwiftValue;
@@ -63,6 +64,7 @@ public Set<String> getSupportedAnnotationTypes() {
6364
annotations.add(com.readdle.codegen.anotation.SwiftValue.class.getCanonicalName());
6465
annotations.add(com.readdle.codegen.anotation.SwiftReference.class.getCanonicalName());
6566
annotations.add(com.readdle.codegen.anotation.SwiftDelegate.class.getCanonicalName());
67+
annotations.add(com.readdle.codegen.anotation.SwiftBlock.class.getCanonicalName());
6668
return annotations;
6769
}
6870

@@ -79,6 +81,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
7981
Map<String, SwiftValueDescriptor> swiftValues = new HashMap<>();
8082
Map<String, SwiftReferenceDescriptor> swiftReferences = new HashMap<>();
8183
Map<String, SwiftDelegateDescriptor> swiftDelegates = new HashMap<>();
84+
Map<String, SwiftBlockDescriptor> swiftBlocks = new HashMap<>();
8285

8386
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(SwiftValue.class)) {
8487
// Check if a class has been annotated with @SwiftValue
@@ -143,6 +146,27 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
143146
}
144147
}
145148

149+
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(SwiftBlock.class)) {
150+
// Check if a class has been annotated with @SwiftValue
151+
if (annotatedElement.getKind() != ElementKind.INTERFACE) {
152+
error(annotatedElement, "Only interface can be annotated with @%s", SwiftBlock.class.getSimpleName());
153+
return true; // Exit processing
154+
}
155+
156+
// We can cast it, because we know that it of ElementKind.CLASS
157+
TypeElement typeElement = (TypeElement) annotatedElement;
158+
159+
try {
160+
SwiftBlockDescriptor blockDescriptor = new SwiftBlockDescriptor(typeElement, filer);
161+
swiftBlocks.put(blockDescriptor.simpleTypeName, blockDescriptor);
162+
}
163+
catch (IllegalArgumentException e) {
164+
e.printStackTrace();
165+
error(annotatedElement, e.getMessage());
166+
return true; // Exit processing
167+
}
168+
}
169+
146170
for (SwiftValueDescriptor valueDescriptor: swiftValues.values()) {
147171

148172
for (SwiftFuncDescriptor function : valueDescriptor.functions) {
@@ -191,6 +215,17 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
191215
}
192216
}
193217

218+
for (SwiftBlockDescriptor blockDescriptor: swiftBlocks.values()) {
219+
try {
220+
File file = blockDescriptor.generateCode();
221+
messager.printMessage(Diagnostic.Kind.NOTE, file.getName() + " generated");
222+
} catch (IOException e) {
223+
e.printStackTrace();
224+
error(null, "Can't write to file: " + e.getMessage());
225+
return true; // Exit processing
226+
}
227+
}
228+
194229
messager.printMessage(Diagnostic.Kind.NOTE, "SwiftJava finished successfully!");
195230

196231
return false;
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
package com.readdle.codegen;
2+
3+
import com.readdle.codegen.anotation.SwiftBlock;
4+
5+
import java.io.File;
6+
import java.io.IOException;
7+
import java.util.LinkedList;
8+
import java.util.List;
9+
10+
import javax.annotation.processing.Filer;
11+
import javax.lang.model.element.Element;
12+
import javax.lang.model.element.ElementKind;
13+
import javax.lang.model.element.ExecutableElement;
14+
import javax.lang.model.element.TypeElement;
15+
import javax.lang.model.element.VariableElement;
16+
import javax.lang.model.type.DeclaredType;
17+
import javax.lang.model.type.MirroredTypeException;
18+
import javax.tools.StandardLocation;
19+
20+
import static com.readdle.codegen.JavaSwiftProcessor.FOLDER;
21+
22+
class SwiftBlockDescriptor {
23+
24+
private static final String SUFFIX = "Android.swift";
25+
private String swiftFilePath;
26+
27+
private TypeElement annotatedClassElement;
28+
private String javaFullName;
29+
String simpleTypeName;
30+
private String[] importPackages;
31+
32+
private String blockSignature;
33+
34+
private String funcName;
35+
private boolean isThrown;
36+
private SwiftEnvironment.Type returnSwiftType;
37+
private boolean isReturnTypeOptional;
38+
private String description;
39+
private String sig;
40+
private List<SwiftParamDescriptor> params = new LinkedList<>();
41+
42+
SwiftBlockDescriptor(TypeElement classElement, Filer filer) throws IllegalArgumentException {
43+
this.annotatedClassElement = classElement;
44+
45+
// Get the full QualifiedTypeName
46+
try {
47+
SwiftBlock annotation = classElement.getAnnotation(SwiftBlock.class);
48+
importPackages = annotation.importPackages();
49+
blockSignature = annotation.signature();
50+
simpleTypeName = classElement.getSimpleName().toString();
51+
javaFullName = classElement.getQualifiedName().toString().replace(".", "/");
52+
53+
} catch (MirroredTypeException mte) {
54+
DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
55+
TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
56+
simpleTypeName = classTypeElement.getSimpleName().toString();
57+
javaFullName = classElement.getQualifiedName().toString().replace(".", "/");
58+
}
59+
60+
try {
61+
swiftFilePath = filer.createResource(StandardLocation.SOURCE_OUTPUT, FOLDER, simpleTypeName + SUFFIX, classElement).toUri().getPath();
62+
} catch (IOException e) {
63+
e.printStackTrace();
64+
throw new IllegalArgumentException("Can't create swift file");
65+
}
66+
67+
Element enclosingElement = classElement.getEnclosingElement();
68+
while (enclosingElement != null && enclosingElement.getKind() == ElementKind.CLASS) {
69+
javaFullName = JavaSwiftProcessor.replaceLast(javaFullName, '/', '$');
70+
enclosingElement = enclosingElement.getEnclosingElement();
71+
}
72+
73+
for (Element element : classElement.getEnclosedElements()) {
74+
if (element.getKind() == ElementKind.METHOD) {
75+
ExecutableElement executableElement = (ExecutableElement) element;
76+
// Except init. We generate it's manually
77+
this.funcName = executableElement.getSimpleName().toString();
78+
this.isThrown = executableElement.getThrownTypes() != null && executableElement.getThrownTypes().size() > 0;
79+
this.returnSwiftType = SwiftEnvironment.parseJavaType(executableElement.getReturnType().toString());
80+
this.isReturnTypeOptional = JavaSwiftProcessor.isNullable(executableElement);
81+
82+
this.sig = "(";
83+
84+
for (VariableElement variableElement : executableElement.getParameters()) {
85+
params.add(new SwiftParamDescriptor(variableElement));
86+
sig += javaClassToSig(variableElement.asType().toString());
87+
}
88+
89+
sig += ")";
90+
91+
if (returnSwiftType != null) {
92+
sig += javaClassToSig(executableElement.getReturnType().toString());
93+
}
94+
else {
95+
sig += "V";
96+
}
97+
break;
98+
}
99+
}
100+
}
101+
102+
File generateCode() throws IOException {
103+
File swiftExtensionFile = new File(swiftFilePath);
104+
SwiftWriter swiftWriter = new SwiftWriter(swiftExtensionFile);
105+
106+
// Write imports
107+
swiftWriter.emitImports(importPackages);
108+
swiftWriter.emitEmptyLine();
109+
110+
swiftWriter.emitStatement(String.format("fileprivate let javaClass = JNI.GlobalFindClass(\"%s\")!", javaFullName));
111+
112+
swiftWriter.emitEmptyLine();
113+
swiftWriter.emit(String.format("public class %s", simpleTypeName));
114+
swiftWriter.emit(" {\n");
115+
116+
swiftWriter.emitEmptyLine();
117+
swiftWriter.emitStatement("let jniObject: jobject");
118+
119+
swiftWriter.emitEmptyLine();
120+
swiftWriter.emitStatement("public init(jniObject: jobject) {");
121+
// TODO: throw exception
122+
swiftWriter.emitStatement("self.jniObject = JNI.api.NewGlobalRef(JNI.env, jniObject)!");
123+
swiftWriter.emitStatement("}");
124+
125+
swiftWriter.emitEmptyLine();
126+
swiftWriter.emitStatement("deinit {");
127+
swiftWriter.emitStatement("JNI.api.DeleteGlobalRef(JNI.env, jniObject)");
128+
swiftWriter.emitStatement("}");
129+
130+
swiftWriter.emitEmptyLine();
131+
swiftWriter.emitStatement("// Create swift object");
132+
swiftWriter.emitStatement(String.format("public static func from(javaObject: jobject) throws -> %s {", blockSignature));
133+
swiftWriter.emitStatement(String.format("return %s(jniObject: javaObject).block", simpleTypeName));
134+
swiftWriter.emitStatement("}");
135+
136+
swiftWriter.emitEmptyLine();
137+
swiftWriter.emitStatement("// Create java object with native pointer");
138+
swiftWriter.emitStatement("public func javaObject() throws -> jobject {");
139+
swiftWriter.emitStatement("return jniObject");
140+
swiftWriter.emitStatement("}");
141+
142+
swiftWriter.emitEmptyLine();
143+
swiftWriter.emitStatement(String.format("static let javaMethod%1$s = try! JNI.getJavaMethod(forClass:\"%2$s\", method: \"%1$s\", sig: \"%3$s\")",
144+
funcName,
145+
javaFullName,
146+
sig));
147+
148+
swiftWriter.emitEmptyLine();
149+
swiftWriter.emitStatement(String.format("public lazy var block: %s = {", blockSignature));
150+
151+
for (SwiftParamDescriptor param : params) {
152+
swiftWriter.emitStatement(String.format("let java_%s: JNIArgumentProtocol", param.name));
153+
}
154+
155+
swiftWriter.emitStatement("do {");
156+
for (int i = 0; i < params.size(); i++) {
157+
SwiftParamDescriptor param = params.get(i);
158+
if (param.isOptional) {
159+
swiftWriter.emitStatement(String.format("if let %s = $%s {", param.name, i + ""));
160+
swiftWriter.emitStatement(String.format("java_%s = try %s.javaObject()", param.name, param.name));
161+
swiftWriter.emitStatement("} else {");
162+
swiftWriter.emitStatement(String.format("java_%s = jnull()", param.name));
163+
swiftWriter.emitStatement("}");
164+
}
165+
else {
166+
swiftWriter.emitStatement(String.format("java_%s = try $%s.javaObject()", param.name, i + ""));
167+
}
168+
}
169+
170+
swiftWriter.emitStatement("}");
171+
swiftWriter.emitStatement("catch {");
172+
swiftWriter.emitStatement("let errorString = String(reflecting: type(of: error)) + String(describing: error)");
173+
if (returnSwiftType == null) {
174+
swiftWriter.emitStatement("assert(false, errorString)");
175+
swiftWriter.emitStatement("return");
176+
}
177+
else {
178+
swiftWriter.emitStatement("fatalError(errorString)");
179+
}
180+
swiftWriter.emitStatement("}");
181+
182+
String jniMethodTemplate;
183+
if (returnSwiftType != null) {
184+
jniMethodTemplate = "guard let result = JNI.CallObjectMethod(self.jniObject, %s.javaMethod%s";
185+
}
186+
else {
187+
jniMethodTemplate = "JNI.CallVoidMethod(self.jniObject, %s.javaMethod%s";
188+
}
189+
190+
swiftWriter.emit(String.format(jniMethodTemplate, simpleTypeName, funcName));
191+
192+
for (SwiftParamDescriptor param : params) {
193+
swiftWriter.emitStatement(String.format(", java_%s", param.name));
194+
}
195+
196+
if (returnSwiftType != null) {
197+
swiftWriter.emit(String.format(") else { %s }\n", isReturnTypeOptional ? "return nil" : "fatalError(\"Don't support nil here!\")"));
198+
}
199+
else {
200+
swiftWriter.emit(")\n");
201+
}
202+
203+
swiftWriter.emitStatement("if let throwable = JNI.ExceptionCheck() {");
204+
if (isThrown) {
205+
swiftWriter.emitStatement("throw NSError(domain: \"JavaException\", code: 1)");
206+
}
207+
else {
208+
swiftWriter.emitStatement("fatalError(\"JavaException\")");
209+
}
210+
swiftWriter.emitStatement("}");
211+
212+
if (returnSwiftType != null) {
213+
swiftWriter.emitStatement("do {");
214+
swiftWriter.emitStatement(String.format("return try %s.from(javaObject: result)", returnSwiftType.swiftConstructorType));
215+
swiftWriter.emitStatement("}");
216+
swiftWriter.emitStatement("catch {");
217+
swiftWriter.emitStatement("let errorString = String(reflecting: type(of: error)) + String(describing: error)");
218+
if (returnSwiftType == null) {
219+
swiftWriter.emitStatement("assert(false, errorString)");
220+
swiftWriter.emitStatement("return");
221+
}
222+
else {
223+
swiftWriter.emitStatement("fatalError(errorString)");
224+
}
225+
swiftWriter.emitStatement("}");
226+
}
227+
228+
swiftWriter.emitStatement("}");
229+
230+
swiftWriter.emitEmptyLine();
231+
232+
swiftWriter.endExtension();
233+
234+
swiftWriter.close();
235+
return swiftExtensionFile;
236+
}
237+
238+
private String javaClassToSig(String javaClass) {
239+
// First, remove all templates
240+
int templateStart = javaClass.indexOf("<");
241+
if (templateStart > 0) {
242+
javaClass = javaClass.substring(0, templateStart);
243+
}
244+
// Replace all dots with / in package name
245+
return "L" + javaClass.replace(".", "/") + ";";
246+
}
247+
}

compiler/src/main/java/com/readdle/codegen/SwiftEnvironment.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
class SwiftEnvironment {
66

77
static class Type {
8-
final String swiftType;
9-
final String javaPackage;
10-
final String swiftConstructorType;
8+
String swiftType;
9+
String javaPackage;
10+
String swiftConstructorType;
1111

1212
Type(String swiftType, String javaPackage) {
1313
this.swiftType = swiftType;

compiler/src/main/java/com/readdle/codegen/SwiftParamDescriptor.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
package com.readdle.codegen;
22

3+
import com.readdle.codegen.anotation.SwiftParam;
4+
35
import javax.lang.model.element.VariableElement;
46

57
public class SwiftParamDescriptor {
68

79
final String name;
8-
final SwiftEnvironment.Type swiftType;
10+
SwiftEnvironment.Type swiftType;
911
final boolean isOptional;
1012
final String description;
1113

1214
SwiftParamDescriptor(VariableElement variableElement) {
1315
this.name = variableElement.getSimpleName().toString();
1416
this.swiftType = SwiftEnvironment.parseJavaType(variableElement.asType().toString());
17+
SwiftParam swiftParam = variableElement.getAnnotation(SwiftParam.class);
18+
if (swiftParam != null) {
19+
this.swiftType.swiftType = swiftParam.value();
20+
}
1521
this.isOptional = JavaSwiftProcessor.isNullable(variableElement);
1622
this.description = null;
1723
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.readdle.codegen.anotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS)
9+
public @interface SwiftBlock {
10+
11+
String[] importPackages() default {};
12+
13+
String signature() default "";
14+
15+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.readdle.codegen.anotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.CLASS)
9+
public @interface SwiftParam {
10+
11+
String value() default "";
12+
13+
}

0 commit comments

Comments
 (0)