During the API review process (bug 246133) the reviewers decided that in order to include html4j to NetBeans Platform, we need to stop using org.apidesign namespace and switch to NetBeans one. Repackaging all SPI packages into org.netbeans.html.smthng.spi.
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
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]"
29 * The Original Software is NetBeans. The Initial Developer of the Original
30 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
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.
43 package org.netbeans.html.boot.impl;
45 import java.io.IOException;
46 import java.io.OutputStream;
47 import java.io.OutputStreamWriter;
48 import java.io.PrintWriter;
49 import java.io.Writer;
50 import java.util.Collections;
51 import java.util.HashMap;
52 import java.util.HashSet;
53 import java.util.List;
56 import java.util.TreeMap;
57 import javax.annotation.processing.AbstractProcessor;
58 import javax.annotation.processing.Completion;
59 import javax.annotation.processing.Completions;
60 import javax.annotation.processing.Messager;
61 import javax.annotation.processing.Processor;
62 import javax.annotation.processing.RoundEnvironment;
63 import javax.lang.model.SourceVersion;
64 import javax.lang.model.element.AnnotationMirror;
65 import javax.lang.model.element.Element;
66 import javax.lang.model.element.ElementKind;
67 import javax.lang.model.element.ExecutableElement;
68 import javax.lang.model.element.Modifier;
69 import javax.lang.model.element.Name;
70 import javax.lang.model.element.PackageElement;
71 import javax.lang.model.element.TypeElement;
72 import javax.lang.model.element.VariableElement;
73 import javax.lang.model.type.ArrayType;
74 import javax.lang.model.type.ExecutableType;
75 import javax.lang.model.type.TypeKind;
76 import javax.lang.model.type.TypeMirror;
77 import javax.tools.Diagnostic;
78 import javax.tools.FileObject;
79 import javax.tools.StandardLocation;
80 import net.java.html.js.JavaScriptBody;
81 import net.java.html.js.JavaScriptResource;
82 import org.openide.util.lookup.ServiceProvider;
86 * @author Jaroslav Tulach
88 @ServiceProvider(service = Processor.class)
89 public final class JavaScriptProcesor extends AbstractProcessor {
90 private final Map<String,Map<String,ExecutableElement>> javacalls =
91 new HashMap<String,Map<String,ExecutableElement>>();
92 private final Map<String,Set<TypeElement>> bodies =
93 new HashMap<String, Set<TypeElement>>();
96 public Set<String> getSupportedAnnotationTypes() {
97 Set<String> set = new HashSet<String>();
98 set.add(JavaScriptBody.class.getName());
99 set.add(JavaScriptResource.class.getName());
104 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
105 final Messager msg = processingEnv.getMessager();
106 for (Element e : roundEnv.getElementsAnnotatedWith(JavaScriptBody.class)) {
107 if (e.getKind() != ElementKind.METHOD && e.getKind() != ElementKind.CONSTRUCTOR) {
110 ExecutableElement ee = (ExecutableElement)e;
111 List<? extends VariableElement> params = ee.getParameters();
113 JavaScriptBody jsb = e.getAnnotation(JavaScriptBody.class);
117 Set<TypeElement> classes = this.bodies.get(findPkg(e));
118 if (classes == null) {
119 classes = new HashSet<TypeElement>();
120 bodies.put(findPkg(e), classes);
122 Element t = e.getEnclosingElement();
123 while (!t.getKind().isClass() && !t.getKind().isInterface()) {
124 t = t.getEnclosingElement();
126 classes.add((TypeElement)t);
128 String[] arr = jsb.args();
129 if (params.size() != arr.length) {
130 msg.printMessage(Diagnostic.Kind.ERROR, "Number of args arguments does not match real arguments!", e);
132 if (!jsb.wait4js() && ee.getReturnType().getKind() != TypeKind.VOID) {
133 msg.printMessage(Diagnostic.Kind.ERROR, "Methods that don't wait for JavaScript to finish must return void!", e);
135 if (!jsb.javacall() && jsb.body().contains(".@")) {
136 msg.printMessage(Diagnostic.Kind.WARNING, "Usage of .@ usually requires javacall=true", e);
138 if (jsb.javacall()) {
139 JsCallback verify = new VerifyCallback(e);
141 verify.parse(jsb.body());
142 } catch (IllegalStateException ex) {
143 msg.printMessage(Diagnostic.Kind.ERROR, ex.getLocalizedMessage(), e);
147 for (Element e : roundEnv.getElementsAnnotatedWith(JavaScriptResource.class)) {
148 JavaScriptResource r = e.getAnnotation(JavaScriptResource.class);
153 if (r.value().startsWith("/")) {
154 res = r.value().substring(1);
156 res = findPkg(e).replace('.', '/') + "/" + r.value();
160 FileObject os = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", res);
161 os.openInputStream().close();
162 } catch (IOException ex1) {
164 FileObject os2 = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", res);
165 os2.openInputStream().close();
166 } catch (IOException ex2) {
168 FileObject os3 = processingEnv.getFiler().getResource(StandardLocation.CLASS_PATH, "", res);
169 os3.openInputStream().close();
170 } catch (IOException ex3) {
171 msg.printMessage(Diagnostic.Kind.ERROR, "Cannot find resource " + res, e);
176 boolean found = false;
177 for (Element mthod : e.getEnclosedElements()) {
178 if (mthod.getKind() != ElementKind.METHOD) {
181 if (mthod.getAnnotation(JavaScriptBody.class) != null) {
187 msg.printMessage(Diagnostic.Kind.ERROR, "At least one method needs @JavaScriptBody annotation. "
188 + "Otherwise it is not guaranteed the resource will ever be loaded,", e
193 if (roundEnv.processingOver()) {
194 generateCallbackClass(javacalls);
195 generateJavaScriptBodyList(bodies);
202 public Iterable<? extends Completion> getCompletions(Element e,
203 AnnotationMirror annotation, ExecutableElement member, String userText
205 StringBuilder sb = new StringBuilder();
206 if (e.getKind() == ElementKind.METHOD && member.getSimpleName().contentEquals("args")) {
207 ExecutableElement ee = (ExecutableElement) e;
210 for (VariableElement ve : ee.getParameters()) {
211 sb.append(sep).append('"').append(ve.getSimpleName())
216 return Collections.nCopies(1, Completions.of(sb.toString()));
221 private class VerifyCallback extends JsCallback {
222 private final Element e;
223 public VerifyCallback(Element e) {
228 protected CharSequence callMethod(String ident, String fqn, String method, String params) {
229 final TypeElement type = processingEnv.getElementUtils().getTypeElement(fqn);
231 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
232 "Callback to non-existing class " + fqn, e
236 ExecutableElement found = null;
237 StringBuilder foundParams = new StringBuilder();
238 for (Element m : type.getEnclosedElements()) {
239 if (m.getKind() != ElementKind.METHOD) {
242 if (m.getSimpleName().contentEquals(method)) {
243 String paramTypes = findParamTypes((ExecutableElement)m);
244 if (paramTypes.equals(params)) {
245 found = (ExecutableElement) m;
248 foundParams.append(paramTypes).append("\n");
252 if (foundParams.length() == 0) {
253 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
254 "Callback to class " + fqn + " with unknown method " + method, e
257 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
258 "Callback to " + fqn + "." + method + " with wrong parameters: " +
259 params + ". Only known parameters are " + foundParams, e
263 Map<String,ExecutableElement> mangledOnes = javacalls.get(findPkg(e));
264 if (mangledOnes == null) {
265 mangledOnes = new TreeMap<String, ExecutableElement>();
266 javacalls.put(findPkg(e), mangledOnes);
268 String mangled = JsCallback.mangle(fqn, method, findParamTypes(found));
269 mangledOnes.put(mangled, found);
274 private String findParamTypes(ExecutableElement method) {
275 ExecutableType t = (ExecutableType) method.asType();
276 StringBuilder sb = new StringBuilder();
278 for (TypeMirror tm : t.getParameterTypes()) {
279 if (tm.getKind().isPrimitive()) {
280 switch (tm.getKind()) {
281 case INT: sb.append('I'); break;
282 case BOOLEAN: sb.append('Z'); break;
283 case BYTE: sb.append('B'); break;
284 case CHAR: sb.append('C'); break;
285 case SHORT: sb.append('S'); break;
286 case DOUBLE: sb.append('D'); break;
287 case FLOAT: sb.append('F'); break;
288 case LONG: sb.append('J'); break;
290 throw new IllegalStateException("Uknown " + tm.getKind());
293 while (tm.getKind() == TypeKind.ARRAY) {
295 tm = ((ArrayType)tm).getComponentType();
298 sb.append(tm.toString().replace('.', '/'));
303 return sb.toString();
307 private void generateJavaScriptBodyList(Map<String,Set<TypeElement>> bodies) {
308 if (bodies.isEmpty()) {
312 FileObject all = processingEnv.getFiler().createResource(
313 StandardLocation.CLASS_OUTPUT, "", "META-INF/net.java.html.js.classes"
315 PrintWriter wAll = new PrintWriter(new OutputStreamWriter(
316 all.openOutputStream(), "UTF-8"
318 for (Map.Entry<String, Set<TypeElement>> entry : bodies.entrySet()) {
319 String pkg = entry.getKey();
320 Set<TypeElement> classes = entry.getValue();
322 FileObject out = processingEnv.getFiler().createResource(
323 StandardLocation.CLASS_OUTPUT, pkg, "net.java.html.js.classes",
324 classes.iterator().next()
326 OutputStream os = out.openOutputStream();
328 PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
329 for (TypeElement type : classes) {
330 final Name bn = processingEnv.getElementUtils().getBinaryName(type);
336 } catch (IOException x) {
337 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write to " + entry.getKey() + ": " + x.toString());
343 } catch (IOException x) {
344 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write to " + "META-INF/net.java.html.js.classes: " + x.toString());
348 private void generateCallbackClass(Map<String,Map<String, ExecutableElement>> process) {
349 for (Map.Entry<String, Map<String, ExecutableElement>> pkgEn : process.entrySet()) {
350 String pkgName = pkgEn.getKey();
351 Map<String, ExecutableElement> map = pkgEn.getValue();
352 StringBuilder source = new StringBuilder();
353 source.append("package ").append(pkgName).append(";\n");
354 source.append("public final class $JsCallbacks$ {\n");
355 source.append(" static final $JsCallbacks$ VM = new $JsCallbacks$(null);\n");
356 source.append(" private final org.netbeans.html.boot.spi.Fn.Presenter p;\n");
357 source.append(" private $JsCallbacks$ last;\n");
358 source.append(" private $JsCallbacks$(org.netbeans.html.boot.spi.Fn.Presenter p) {\n");
359 source.append(" this.p = p;\n");
360 source.append(" }\n");
361 source.append(" final $JsCallbacks$ current() {\n");
362 source.append(" org.netbeans.html.boot.spi.Fn.Presenter now = org.netbeans.html.boot.spi.Fn.activePresenter();\n");
363 source.append(" if (now == p) return this;\n");
364 source.append(" if (last != null && now == last.p) return last;\n");
365 source.append(" return last = new $JsCallbacks$(now);\n");
366 source.append(" }\n");
367 for (Map.Entry<String, ExecutableElement> entry : map.entrySet()) {
368 final String mangled = entry.getKey();
369 final ExecutableElement m = entry.getValue();
370 final boolean isStatic = m.getModifiers().contains(Modifier.STATIC);
372 source.append("\n public java.lang.Object ")
378 source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
379 source.append(" self");
384 StringBuilder convert = new StringBuilder();
385 for (VariableElement ve : m.getParameters()) {
388 final TypeMirror t = ve.asType();
389 if (!t.getKind().isPrimitive()) {
390 source.append("Object");
391 convert.append(" if (p instanceof org.netbeans.html.boot.spi.Fn.FromJavaScript) {\n");
392 convert.append(" arg").append(cnt).
393 append(" = ((org.netbeans.html.boot.spi.Fn.FromJavaScript)p).toJava(arg").append(cnt).
395 convert.append(" }\n");
399 source.append(" arg").append(cnt);
402 source.append(") throws Throwable {\n");
403 source.append(convert);
404 if (useTryResources()) {
405 source.append(" try (java.io.Closeable a = org.netbeans.html.boot.spi.Fn.activate(p)) { \n");
407 source.append(" java.io.Closeable a = org.netbeans.html.boot.spi.Fn.activate(p); try {\n");
410 if (m.getReturnType().getKind() != TypeKind.VOID) {
411 source.append("Object $ret = ");
414 source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
417 source.append("self.");
419 source.append(m.getSimpleName());
423 for (VariableElement ve : m.getParameters()) {
425 source.append("(").append(ve.asType());
426 source.append(")arg").append(++cnt);
429 source.append(");\n");
430 if (m.getReturnType().getKind() == TypeKind.VOID) {
431 source.append(" return null;\n");
433 source.append(" if (p instanceof org.netbeans.html.boot.spi.Fn.ToJavaScript) {\n");
434 source.append(" $ret = ((org.netbeans.html.boot.spi.Fn.ToJavaScript)p).toJavaScript($ret);\n");
435 source.append(" }\n");
436 source.append(" return $ret;\n");
438 if (useTryResources()) {
439 source.append(" }\n");
442 source.append(" } finally {\n");
443 source.append(" a.close();\n");
444 source.append(" }\n");
446 source.append(" }\n");
448 source.append("}\n");
449 final String srcName = pkgName + ".$JsCallbacks$";
451 Writer w = processingEnv.getFiler().createSourceFile(srcName,
452 map.values().toArray(new Element[map.size()])
454 w.write(source.toString());
456 } catch (IOException ex) {
457 processingEnv.getMessager().printMessage(
458 Diagnostic.Kind.ERROR, "Can't write " + srcName + ": " + ex.getMessage()
464 private boolean useTryResources() {
466 return processingEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_7) >= 0;
467 } catch (LinkageError err) {
468 // can happen when running on JDK6
473 private static String findPkg(Element e) {
474 while (e.getKind() != ElementKind.PACKAGE) {
475 e = e.getEnclosingElement();
477 return ((PackageElement)e).getQualifiedName().toString();