For each package generate $JsCallbacks$ class that encapsulates all calls from JavaScript to Java
1.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/JavaScriptProcesor.java Thu Jul 11 14:47:58 2013 +0200
1.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JavaScriptProcesor.java Thu Jul 11 16:33:40 2013 +0200
1.3 @@ -20,11 +20,16 @@
1.4 */
1.5 package org.apidesign.html.boot.impl;
1.6
1.7 +import java.io.IOException;
1.8 +import java.io.Writer;
1.9 +import java.util.ArrayList;
1.10 import java.util.Collections;
1.11 +import java.util.HashMap;
1.12 import java.util.HashSet;
1.13 import java.util.List;
1.14 import java.util.Map;
1.15 import java.util.Set;
1.16 +import java.util.TreeMap;
1.17 import javax.annotation.processing.AbstractProcessor;
1.18 import javax.annotation.processing.Completion;
1.19 import javax.annotation.processing.Completions;
1.20 @@ -35,6 +40,8 @@
1.21 import javax.lang.model.element.Element;
1.22 import javax.lang.model.element.ElementKind;
1.23 import javax.lang.model.element.ExecutableElement;
1.24 +import javax.lang.model.element.Modifier;
1.25 +import javax.lang.model.element.PackageElement;
1.26 import javax.lang.model.element.TypeElement;
1.27 import javax.lang.model.element.VariableElement;
1.28 import javax.lang.model.type.ArrayType;
1.29 @@ -52,6 +59,9 @@
1.30 */
1.31 @ServiceProvider(service = Processor.class)
1.32 public final class JavaScriptProcesor extends AbstractProcessor {
1.33 + private final Map<String,Map<String,ExecutableElement>> javacalls =
1.34 + new HashMap<String,Map<String,ExecutableElement>>();
1.35 +
1.36 @Override
1.37 public Set<String> getSupportedAnnotationTypes() {
1.38 Set<String> set = new HashSet<String>();
1.39 @@ -86,6 +96,10 @@
1.40 verify.parse(jsb.body());
1.41 }
1.42 }
1.43 + if (roundEnv.processingOver()) {
1.44 + generateCallbackClass(javacalls);
1.45 + javacalls.clear();
1.46 + }
1.47 return true;
1.48 }
1.49
1.50 @@ -109,7 +123,6 @@
1.51 return null;
1.52 }
1.53
1.54 -
1.55 private class VerifyCallback extends JsCallback {
1.56 private final Element e;
1.57 public VerifyCallback(Element e) {
1.58 @@ -125,16 +138,16 @@
1.59 );
1.60 return "";
1.61 }
1.62 - Element found = null;
1.63 + ExecutableElement found = null;
1.64 StringBuilder foundParams = new StringBuilder();
1.65 for (Element m : type.getEnclosedElements()) {
1.66 if (m.getKind() != ElementKind.METHOD) {
1.67 continue;
1.68 }
1.69 if (m.getSimpleName().contentEquals(method)) {
1.70 - String paramTypes = findParamTypes((ExecutableElement)m);
1.71 + String paramTypes = findParamTypes((ExecutableElement)m, true);
1.72 if (paramTypes.equals(params)) {
1.73 - found = m;
1.74 + found = (ExecutableElement) m;
1.75 break;
1.76 }
1.77 foundParams.append(paramTypes).append("\n");
1.78 @@ -151,14 +164,24 @@
1.79 params + ". Only known parameters are " + foundParams, e
1.80 );
1.81 }
1.82 + } else {
1.83 + Map<String,ExecutableElement> mangledOnes = javacalls.get(findPkg(e));
1.84 + if (mangledOnes == null) {
1.85 + mangledOnes = new TreeMap<String, ExecutableElement>();
1.86 + javacalls.put(findPkg(e), mangledOnes);
1.87 + }
1.88 + String mangled = JsCallback.mangle(fqn, method, findParamTypes(found, false));
1.89 + mangledOnes.put(mangled, found);
1.90 }
1.91 return "";
1.92 }
1.93
1.94 - private String findParamTypes(ExecutableElement method) {
1.95 + private String findParamTypes(ExecutableElement method, boolean surround) {
1.96 ExecutableType t = (ExecutableType) method.asType();
1.97 StringBuilder sb = new StringBuilder();
1.98 - sb.append('(');
1.99 + if (surround) {
1.100 + sb.append('(');
1.101 + }
1.102 for (TypeMirror tm : t.getParameterTypes()) {
1.103 if (tm.getKind().isPrimitive()) {
1.104 switch (tm.getKind()) {
1.105 @@ -183,9 +206,88 @@
1.106 sb.append(';');
1.107 }
1.108 }
1.109 - sb.append(')');
1.110 + if (surround) {
1.111 + sb.append(')');
1.112 + }
1.113 return sb.toString();
1.114 }
1.115 -
1.116 }
1.117 +
1.118 + private void generateCallbackClass(Map<String,Map<String, ExecutableElement>> process) {
1.119 + for (Map.Entry<String, Map<String, ExecutableElement>> pkgEn : process.entrySet()) {
1.120 + String pkgName = pkgEn.getKey();
1.121 + Map<String, ExecutableElement> map = pkgEn.getValue();
1.122 + StringBuilder source = new StringBuilder();
1.123 + source.append("package ").append(pkgName).append(";\n");
1.124 + source.append("final class $JsCallbacks$ {\n");
1.125 + for (Map.Entry<String, ExecutableElement> entry : map.entrySet()) {
1.126 + final String mangled = entry.getKey();
1.127 + final ExecutableElement m = entry.getValue();
1.128 + final boolean isStatic = m.getModifiers().contains(Modifier.STATIC);
1.129 +
1.130 + source.append("\n public java.lang.Object ")
1.131 + .append(mangled)
1.132 + .append("(");
1.133 +
1.134 + String sep = "";
1.135 + if (!isStatic) {
1.136 + source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
1.137 + source.append(" self");
1.138 + sep = ", ";
1.139 + }
1.140 +
1.141 + int cnt = 0;
1.142 + for (VariableElement ve : m.getParameters()) {
1.143 + source.append(sep);
1.144 + source.append(ve.asType());
1.145 + source.append(" arg").append(++cnt);
1.146 + sep = ", ";
1.147 + }
1.148 + source.append(") {\n");
1.149 + source.append(" ");
1.150 + if (m.getReturnType().getKind() != TypeKind.VOID) {
1.151 + source.append("return ");
1.152 + }
1.153 + if (!isStatic) {
1.154 + source.append("self.");
1.155 + }
1.156 + source.append(m.getSimpleName());
1.157 + source.append("(");
1.158 + cnt = 0;
1.159 + sep = "";
1.160 + for (VariableElement ve : m.getParameters()) {
1.161 + source.append(sep);
1.162 + source.append("arg").append(++cnt);
1.163 + sep = ", ";
1.164 + }
1.165 + source.append(");\n");
1.166 + if (m.getReturnType().getKind() == TypeKind.VOID) {
1.167 + source.append(" return null;\n");
1.168 + }
1.169 + source.append(" }\n");
1.170 + }
1.171 + source.append("}\n");
1.172 + final String srcName = pkgName + ".$JsCallbacks$";
1.173 + try {
1.174 + Writer w = processingEnv.getFiler().createSourceFile(srcName,
1.175 + map.values().toArray(new Element[map.size()])
1.176 + ).openWriter();
1.177 + w.write(source.toString());
1.178 + w.close();
1.179 + return;
1.180 + } catch (IOException ex) {
1.181 + processingEnv.getMessager().printMessage(
1.182 + Diagnostic.Kind.ERROR, "Can't write " + srcName + ": " + ex.getMessage()
1.183 + );
1.184 + }
1.185 + }
1.186 + }
1.187 +
1.188 + private static String findPkg(Element e) {
1.189 + while (e.getKind() != ElementKind.PACKAGE) {
1.190 + e = e.getEnclosingElement();
1.191 + }
1.192 + return ((PackageElement)e).getQualifiedName().toString();
1.193 + }
1.194 +
1.195 }
2.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/JsCallback.java Thu Jul 11 14:47:58 2013 +0200
2.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsCallback.java Thu Jul 11 16:33:40 2013 +0200
2.3 @@ -61,4 +61,16 @@
2.4 protected abstract CharSequence callMethod(
2.5 String ident, String fqn, String method, String params
2.6 );
2.7 +
2.8 + static String mangle(String fqn, String method, String params) {
2.9 + return
2.10 + replace(fqn) + "__" + replace(method) + "__" + replace(params);
2.11 + }
2.12 +
2.13 + private static String replace(String orig) {
2.14 + return orig.replace("_", "_1").
2.15 + replace(";", "_2").
2.16 + replace("[", "_3").
2.17 + replace('.', '_').replace('/', '_');
2.18 + }
2.19 }
3.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/JavaScriptProcesorTest.java Thu Jul 11 14:47:58 2013 +0200
3.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/JavaScriptProcesorTest.java Thu Jul 11 16:33:40 2013 +0200
3.3 @@ -21,6 +21,8 @@
3.4 package org.apidesign.html.boot.impl;
3.5
3.6 import java.io.IOException;
3.7 +import java.lang.reflect.Method;
3.8 +import static org.testng.Assert.assertEquals;
3.9 import org.testng.annotations.Test;
3.10
3.11 /**
3.12 @@ -88,4 +90,9 @@
3.13 c.assertNoErrors();
3.14 }
3.15
3.16 + @Test public void generatesCallbacksThatReturnObject() throws Exception {
3.17 + Class<?> callbacksForTestPkg = Class.forName("org.apidesign.html.boot.impl.$JsCallbacks$");
3.18 + Method m = callbacksForTestPkg.getDeclaredMethod("java_lang_Runnable__run__", Runnable.class);
3.19 + assertEquals(m.getReturnType(), Object.class, "All methods always return object");
3.20 + }
3.21 }