For each package generate $JsCallbacks$ class that encapsulates all calls from JavaScript to Java
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 11 Jul 2013 16:33:40 +0200
changeset 186e5dc51fadf4a
parent 185 59b56e7eb7de
child 187 9fed4172ad33
For each package generate $JsCallbacks$ class that encapsulates all calls from JavaScript to Java
boot/src/main/java/org/apidesign/html/boot/impl/JavaScriptProcesor.java
boot/src/main/java/org/apidesign/html/boot/impl/JsCallback.java
boot/src/test/java/org/apidesign/html/boot/impl/JavaScriptProcesorTest.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  }