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