boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Mon, 16 Dec 2013 16:59:43 +0100
branchnetbeans
changeset 362 92fb71afdc0e
parent 358 boot/src/main/java/org/apidesign/html/boot/impl/JavaScriptProcesor.java@80702021b851
child 365 5c93ad8c7a15
permissions -rw-r--r--
Moving implementation classes into org.netbeans.html namespace
     1 /**
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
     5  *
     6  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
     7  * Other names may be trademarks of their respective owners.
     8  *
     9  * The contents of this file are subject to the terms of either the GNU
    10  * General Public License Version 2 only ("GPL") or the Common
    11  * Development and Distribution License("CDDL") (collectively, the
    12  * "License"). You may not use this file except in compliance with the
    13  * License. You can obtain a copy of the License at
    14  * http://www.netbeans.org/cddl-gplv2.html
    15  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    16  * specific language governing permissions and limitations under the
    17  * License.  When distributing the software, include this License Header
    18  * Notice in each file and include the License file at
    19  * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    20  * particular file as subject to the "Classpath" exception as provided
    21  * by Oracle in the GPL Version 2 section of the License file that
    22  * accompanied this code. If applicable, add the following below the
    23  * License Header, with the fields enclosed by brackets [] replaced by
    24  * your own identifying information:
    25  * "Portions Copyrighted [year] [name of copyright owner]"
    26  *
    27  * Contributor(s):
    28  *
    29  * The Original Software is NetBeans. The Initial Developer of the Original
    30  * Software is Oracle. Portions Copyright 2013-2013 Oracle. All Rights Reserved.
    31  *
    32  * If you wish your version of this file to be governed by only the CDDL
    33  * or only the GPL Version 2, indicate your decision by adding
    34  * "[Contributor] elects to include this software in this distribution
    35  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    36  * single choice of license, a recipient has the option to distribute
    37  * your version of this file under either the CDDL, the GPL Version 2 or
    38  * to extend the choice of license to its licensees as provided above.
    39  * However, if you add GPL Version 2 code and therefore, elected the GPL
    40  * Version 2 license, then the option applies only if the new code is
    41  * made subject to such option by the copyright holder.
    42  */
    43 package org.netbeans.html.boot.impl;
    44 
    45 import java.io.IOException;
    46 import java.io.Writer;
    47 import java.util.Collections;
    48 import java.util.HashMap;
    49 import java.util.HashSet;
    50 import java.util.List;
    51 import java.util.Map;
    52 import java.util.Set;
    53 import java.util.TreeMap;
    54 import javax.annotation.processing.AbstractProcessor;
    55 import javax.annotation.processing.Completion;
    56 import javax.annotation.processing.Completions;
    57 import javax.annotation.processing.Messager;
    58 import javax.annotation.processing.Processor;
    59 import javax.annotation.processing.RoundEnvironment;
    60 import javax.lang.model.SourceVersion;
    61 import javax.lang.model.element.AnnotationMirror;
    62 import javax.lang.model.element.Element;
    63 import javax.lang.model.element.ElementKind;
    64 import javax.lang.model.element.ExecutableElement;
    65 import javax.lang.model.element.Modifier;
    66 import javax.lang.model.element.PackageElement;
    67 import javax.lang.model.element.TypeElement;
    68 import javax.lang.model.element.VariableElement;
    69 import javax.lang.model.type.ArrayType;
    70 import javax.lang.model.type.ExecutableType;
    71 import javax.lang.model.type.TypeKind;
    72 import javax.lang.model.type.TypeMirror;
    73 import javax.tools.Diagnostic;
    74 import javax.tools.FileObject;
    75 import javax.tools.StandardLocation;
    76 import net.java.html.js.JavaScriptBody;
    77 import net.java.html.js.JavaScriptResource;
    78 import org.openide.util.lookup.ServiceProvider;
    79 
    80 /**
    81  *
    82  * @author Jaroslav Tulach <jtulach@netbeans.org>
    83  */
    84 @ServiceProvider(service = Processor.class)
    85 public final class JavaScriptProcesor extends AbstractProcessor {
    86     private final Map<String,Map<String,ExecutableElement>> javacalls = 
    87         new HashMap<String,Map<String,ExecutableElement>>();
    88     
    89     @Override
    90     public Set<String> getSupportedAnnotationTypes() {
    91         Set<String> set = new HashSet<String>();
    92         set.add(JavaScriptBody.class.getName());
    93         set.add(JavaScriptResource.class.getName());
    94         return set;
    95     }
    96     
    97     @Override
    98     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    99         final Messager msg = processingEnv.getMessager();
   100         for (Element e : roundEnv.getElementsAnnotatedWith(JavaScriptBody.class)) {
   101             if (e.getKind() != ElementKind.METHOD && e.getKind() != ElementKind.CONSTRUCTOR) {
   102                 continue;
   103             }
   104             ExecutableElement ee = (ExecutableElement)e;
   105             List<? extends VariableElement> params = ee.getParameters();
   106             
   107             JavaScriptBody jsb = e.getAnnotation(JavaScriptBody.class);
   108             if (jsb == null) {
   109                 continue;
   110             }
   111             String[] arr = jsb.args();
   112             if (params.size() != arr.length) {
   113                 msg.printMessage(Diagnostic.Kind.ERROR, "Number of args arguments does not match real arguments!", e);
   114             }
   115             if (!jsb.javacall() && jsb.body().contains(".@")) {
   116                 msg.printMessage(Diagnostic.Kind.WARNING, "Usage of .@ usually requires javacall=true", e);
   117             }
   118             if (jsb.javacall()) {
   119                 JsCallback verify = new VerifyCallback(e);
   120                 try {
   121                     verify.parse(jsb.body());
   122                 } catch (IllegalStateException ex) {
   123                     msg.printMessage(Diagnostic.Kind.ERROR, ex.getLocalizedMessage(), e);
   124                 }
   125             }
   126         }
   127         for (Element e : roundEnv.getElementsAnnotatedWith(JavaScriptResource.class)) {
   128             JavaScriptResource r = e.getAnnotation(JavaScriptResource.class);
   129             if (r == null) {
   130                 continue;
   131             }
   132             final String res;
   133             if (r.value().startsWith("/")) {
   134                 res = r.value();
   135             } else {
   136                 res = findPkg(e).replace('.', '/') + "/" + r.value();
   137             }
   138             
   139             try {
   140                 FileObject os = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", res);
   141                 os.openInputStream().close();
   142             } catch (IOException ex1) {
   143                 try {
   144                     FileObject os2 = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", res);
   145                     os2.openInputStream().close();
   146                 } catch (IOException ex2) {
   147                     msg.printMessage(Diagnostic.Kind.ERROR, "Cannot find " + res + " in " + res + " package", e);
   148                 }
   149             }
   150         }
   151 
   152         if (roundEnv.processingOver()) {
   153             generateCallbackClass(javacalls);
   154             javacalls.clear();
   155         }
   156         return true;
   157     }
   158 
   159     @Override
   160     public Iterable<? extends Completion> getCompletions(Element e, 
   161         AnnotationMirror annotation, ExecutableElement member, String userText
   162     ) {
   163         StringBuilder sb = new StringBuilder();
   164         if (e.getKind() == ElementKind.METHOD && member.getSimpleName().contentEquals("args")) {
   165             ExecutableElement ee = (ExecutableElement) e;
   166             String sep = "";
   167             sb.append("{ ");
   168             for (VariableElement ve : ee.getParameters()) {
   169                 sb.append(sep).append('"').append(ve.getSimpleName())
   170                     .append('"');
   171                 sep = ", ";
   172             }
   173             sb.append(" }");
   174             return Collections.nCopies(1, Completions.of(sb.toString()));
   175         }
   176         return null;
   177     }
   178 
   179     private class VerifyCallback extends JsCallback {
   180         private final Element e;
   181         public VerifyCallback(Element e) {
   182             this.e = e;
   183         }
   184 
   185         @Override
   186         protected CharSequence callMethod(String ident, String fqn, String method, String params) {
   187             final TypeElement type = processingEnv.getElementUtils().getTypeElement(fqn);
   188             if (type == null) {
   189                 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   190                     "Callback to non-existing class " + fqn, e
   191                 );
   192                 return "";
   193             }
   194             ExecutableElement found = null;
   195             StringBuilder foundParams = new StringBuilder();
   196             for (Element m : type.getEnclosedElements()) {
   197                 if (m.getKind() != ElementKind.METHOD) {
   198                     continue;
   199                 }
   200                 if (m.getSimpleName().contentEquals(method)) {
   201                     String paramTypes = findParamTypes((ExecutableElement)m);
   202                     if (paramTypes.equals(params)) {
   203                         found = (ExecutableElement) m;
   204                         break;
   205                     }
   206                     foundParams.append(paramTypes).append("\n");
   207                 }
   208             }
   209             if (found == null) {
   210                 if (foundParams.length() == 0) {
   211                     processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   212                         "Callback to class " + fqn + " with unknown method " + method, e
   213                     );
   214                 } else {
   215                     processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   216                         "Callback to " + fqn + "." + method + " with wrong parameters: " + 
   217                         params + ". Only known parameters are " + foundParams, e
   218                     );
   219                 }
   220             } else {
   221                 Map<String,ExecutableElement> mangledOnes = javacalls.get(findPkg(e));
   222                 if (mangledOnes == null) {
   223                     mangledOnes = new TreeMap<String, ExecutableElement>();
   224                     javacalls.put(findPkg(e), mangledOnes);
   225                 }
   226                 String mangled = JsCallback.mangle(fqn, method, findParamTypes(found));
   227                 mangledOnes.put(mangled, found);
   228             }
   229             return "";
   230         }
   231 
   232         private String findParamTypes(ExecutableElement method) {
   233             ExecutableType t = (ExecutableType) method.asType();
   234             StringBuilder sb = new StringBuilder();
   235             sb.append('(');
   236             for (TypeMirror tm : t.getParameterTypes()) {
   237                 if (tm.getKind().isPrimitive()) {
   238                     switch (tm.getKind()) {
   239                         case INT: sb.append('I'); break;
   240                         case BOOLEAN: sb.append('Z'); break;
   241                         case BYTE: sb.append('B'); break;
   242                         case CHAR: sb.append('C'); break;
   243                         case SHORT: sb.append('S'); break;
   244                         case DOUBLE: sb.append('D'); break;
   245                         case FLOAT: sb.append('F'); break;
   246                         case LONG: sb.append('J'); break;
   247                         default:
   248                             throw new IllegalStateException("Uknown " + tm.getKind());
   249                     }
   250                 } else {
   251                     while (tm.getKind() == TypeKind.ARRAY) {
   252                         sb.append('[');
   253                         tm = ((ArrayType)tm).getComponentType();
   254                     }
   255                     sb.append('L');
   256                     sb.append(tm.toString().replace('.', '/'));
   257                     sb.append(';');
   258                 }
   259             }
   260             sb.append(')');
   261             return sb.toString();
   262         }
   263     }
   264     
   265     private void generateCallbackClass(Map<String,Map<String, ExecutableElement>> process) {
   266         for (Map.Entry<String, Map<String, ExecutableElement>> pkgEn : process.entrySet()) {
   267             String pkgName = pkgEn.getKey();
   268             Map<String, ExecutableElement> map = pkgEn.getValue();
   269             StringBuilder source = new StringBuilder();
   270             source.append("package ").append(pkgName).append(";\n");
   271             source.append("public final class $JsCallbacks$ {\n");
   272             source.append("  static final $JsCallbacks$ VM = new $JsCallbacks$(null);\n");
   273             source.append("  private final org.apidesign.html.boot.spi.Fn.Presenter p;\n");
   274             source.append("  private $JsCallbacks$ last;\n");
   275             source.append("  private $JsCallbacks$(org.apidesign.html.boot.spi.Fn.Presenter p) {\n");
   276             source.append("    this.p = p;\n");
   277             source.append("  }\n");
   278             source.append("  final $JsCallbacks$ current() {\n");
   279             source.append("    org.apidesign.html.boot.spi.Fn.Presenter now = org.apidesign.html.boot.spi.Fn.activePresenter();\n");
   280             source.append("    if (now == p) return this;\n");
   281             source.append("    if (last != null && now == last.p) return last;\n");
   282             source.append("    return last = new $JsCallbacks$(now);\n");
   283             source.append("  }\n");
   284             for (Map.Entry<String, ExecutableElement> entry : map.entrySet()) {
   285                 final String mangled = entry.getKey();
   286                 final ExecutableElement m = entry.getValue();
   287                 final boolean isStatic = m.getModifiers().contains(Modifier.STATIC);
   288                 
   289                 source.append("\n  public java.lang.Object ")
   290                     .append(mangled)
   291                     .append("(");
   292                 
   293                 String sep = "";
   294                 if (!isStatic) {
   295                     source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
   296                     source.append(" self");
   297                     sep = ", ";
   298                 }
   299                 
   300                 int cnt = 0;
   301                 for (VariableElement ve : m.getParameters()) {
   302                     source.append(sep);
   303                     source.append(ve.asType());
   304                     source.append(" arg").append(++cnt);
   305                     sep = ", ";
   306                 }
   307                 source.append(") throws Throwable {\n");
   308                 if (processingEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_7) >= 0) {
   309                     source.append("    try (java.io.Closeable a = org.apidesign.html.boot.spi.Fn.activate(p)) { \n");
   310                 } else {
   311                     source.append("    java.io.Closeable a = org.apidesign.html.boot.spi.Fn.activate(p); try {\n");
   312                 }
   313                 source.append("    ");
   314                 if (m.getReturnType().getKind() != TypeKind.VOID) {
   315                     source.append("return ");
   316                 }
   317                 if (isStatic) {
   318                     source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
   319                     source.append('.');
   320                 } else {
   321                     source.append("self.");
   322                 }
   323                 source.append(m.getSimpleName());
   324                 source.append("(");
   325                 cnt = 0;
   326                 sep = "";
   327                 for (VariableElement ve : m.getParameters()) {
   328                     source.append(sep);
   329                     source.append("arg").append(++cnt);
   330                     sep = ", ";
   331                 }
   332                 source.append(");\n");
   333                 if (m.getReturnType().getKind() == TypeKind.VOID) {
   334                     source.append("    return null;\n");
   335                 }
   336                 if (processingEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_7) >= 0) {
   337                     source.append("    }\n");
   338                 } else {
   339                     
   340                     source.append("    } finally {\n");
   341                     source.append("      a.close();\n");
   342                     source.append("    }\n");
   343                 }
   344                 source.append("  }\n");
   345             }
   346             source.append("}\n");
   347             final String srcName = pkgName + ".$JsCallbacks$";
   348             try {
   349                 Writer w = processingEnv.getFiler().createSourceFile(srcName,
   350                     map.values().toArray(new Element[map.size()])
   351                 ).openWriter();
   352                 w.write(source.toString());
   353                 w.close();
   354             } catch (IOException ex) {
   355                 processingEnv.getMessager().printMessage(
   356                     Diagnostic.Kind.ERROR, "Can't write " + srcName + ": " + ex.getMessage()
   357                 );
   358             }
   359         }
   360     }
   361     
   362     private static String findPkg(Element e) {
   363         while (e.getKind() != ElementKind.PACKAGE) {
   364             e = e.getEnclosingElement();
   365         }
   366         return ((PackageElement)e).getQualifiedName().toString();
   367     }
   368     
   369 }