Exposing Fn.activate and Fn.activePresenter so they are available from exported packages
2 * HTML via Java(tm) Language Bindings
3 * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 2 of the License.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. apidesign.org
13 * designates this particular file as subject to the
14 * "Classpath" exception as provided by apidesign.org
15 * in the License file that accompanied this code.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. Look for COPYING file in the top folder.
19 * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
21 package org.apidesign.html.boot.impl;
23 import java.io.IOException;
24 import java.io.Writer;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
31 import java.util.TreeMap;
32 import javax.annotation.processing.AbstractProcessor;
33 import javax.annotation.processing.Completion;
34 import javax.annotation.processing.Completions;
35 import javax.annotation.processing.Messager;
36 import javax.annotation.processing.Processor;
37 import javax.annotation.processing.RoundEnvironment;
38 import javax.lang.model.element.AnnotationMirror;
39 import javax.lang.model.element.Element;
40 import javax.lang.model.element.ElementKind;
41 import javax.lang.model.element.ExecutableElement;
42 import javax.lang.model.element.Modifier;
43 import javax.lang.model.element.PackageElement;
44 import javax.lang.model.element.TypeElement;
45 import javax.lang.model.element.VariableElement;
46 import javax.lang.model.type.ArrayType;
47 import javax.lang.model.type.ExecutableType;
48 import javax.lang.model.type.TypeKind;
49 import javax.lang.model.type.TypeMirror;
50 import javax.tools.Diagnostic;
51 import net.java.html.js.JavaScriptBody;
52 import net.java.html.js.JavaScriptResource;
53 import org.openide.util.lookup.ServiceProvider;
57 * @author Jaroslav Tulach <jtulach@netbeans.org>
59 @ServiceProvider(service = Processor.class)
60 public final class JavaScriptProcesor extends AbstractProcessor {
61 private final Map<String,Map<String,ExecutableElement>> javacalls =
62 new HashMap<String,Map<String,ExecutableElement>>();
65 public Set<String> getSupportedAnnotationTypes() {
66 Set<String> set = new HashSet<String>();
67 set.add(JavaScriptBody.class.getName());
68 set.add(JavaScriptResource.class.getName());
73 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
74 final Messager msg = processingEnv.getMessager();
75 for (Element e : roundEnv.getElementsAnnotatedWith(JavaScriptBody.class)) {
76 if (e.getKind() != ElementKind.METHOD && e.getKind() != ElementKind.CONSTRUCTOR) {
79 ExecutableElement ee = (ExecutableElement)e;
80 List<? extends VariableElement> params = ee.getParameters();
82 JavaScriptBody jsb = e.getAnnotation(JavaScriptBody.class);
86 String[] arr = jsb.args();
87 if (params.size() != arr.length) {
88 msg.printMessage(Diagnostic.Kind.ERROR, "Number of args arguments does not match real arguments!", e);
90 if (!jsb.javacall() && jsb.body().contains(".@")) {
91 msg.printMessage(Diagnostic.Kind.WARNING, "Usage of .@ usually requires javacall=true", e);
94 JsCallback verify = new VerifyCallback(e);
96 verify.parse(jsb.body());
97 } catch (IllegalStateException ex) {
98 msg.printMessage(Diagnostic.Kind.ERROR, ex.getLocalizedMessage(), e);
102 if (roundEnv.processingOver()) {
103 generateCallbackClass(javacalls);
110 public Iterable<? extends Completion> getCompletions(Element e,
111 AnnotationMirror annotation, ExecutableElement member, String userText
113 StringBuilder sb = new StringBuilder();
114 if (e.getKind() == ElementKind.METHOD && member.getSimpleName().contentEquals("args")) {
115 ExecutableElement ee = (ExecutableElement) e;
118 for (VariableElement ve : ee.getParameters()) {
119 sb.append(sep).append('"').append(ve.getSimpleName())
124 return Collections.nCopies(1, Completions.of(sb.toString()));
129 private class VerifyCallback extends JsCallback {
130 private final Element e;
131 public VerifyCallback(Element e) {
136 protected CharSequence callMethod(String ident, String fqn, String method, String params) {
137 final TypeElement type = processingEnv.getElementUtils().getTypeElement(fqn);
139 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
140 "Callback to non-existing class " + fqn, e
144 ExecutableElement found = null;
145 StringBuilder foundParams = new StringBuilder();
146 for (Element m : type.getEnclosedElements()) {
147 if (m.getKind() != ElementKind.METHOD) {
150 if (m.getSimpleName().contentEquals(method)) {
151 String paramTypes = findParamTypes((ExecutableElement)m);
152 if (paramTypes.equals(params)) {
153 found = (ExecutableElement) m;
156 foundParams.append(paramTypes).append("\n");
160 if (foundParams.length() == 0) {
161 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
162 "Callback to class " + fqn + " with unknown method " + method, e
165 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
166 "Callback to " + fqn + "." + method + " with wrong parameters: " +
167 params + ". Only known parameters are " + foundParams, e
171 Map<String,ExecutableElement> mangledOnes = javacalls.get(findPkg(e));
172 if (mangledOnes == null) {
173 mangledOnes = new TreeMap<String, ExecutableElement>();
174 javacalls.put(findPkg(e), mangledOnes);
176 String mangled = JsCallback.mangle(fqn, method, findParamTypes(found));
177 mangledOnes.put(mangled, found);
182 private String findParamTypes(ExecutableElement method) {
183 ExecutableType t = (ExecutableType) method.asType();
184 StringBuilder sb = new StringBuilder();
186 for (TypeMirror tm : t.getParameterTypes()) {
187 if (tm.getKind().isPrimitive()) {
188 switch (tm.getKind()) {
189 case INT: sb.append('I'); break;
190 case BOOLEAN: sb.append('Z'); break;
191 case BYTE: sb.append('B'); break;
192 case CHAR: sb.append('C'); break;
193 case SHORT: sb.append('S'); break;
194 case DOUBLE: sb.append('D'); break;
195 case FLOAT: sb.append('F'); break;
196 case LONG: sb.append('J'); break;
198 throw new IllegalStateException("Uknown " + tm.getKind());
201 while (tm.getKind() == TypeKind.ARRAY) {
203 tm = ((ArrayType)tm).getComponentType();
206 sb.append(tm.toString().replace('.', '/'));
211 return sb.toString();
215 private void generateCallbackClass(Map<String,Map<String, ExecutableElement>> process) {
216 for (Map.Entry<String, Map<String, ExecutableElement>> pkgEn : process.entrySet()) {
217 String pkgName = pkgEn.getKey();
218 Map<String, ExecutableElement> map = pkgEn.getValue();
219 StringBuilder source = new StringBuilder();
220 source.append("package ").append(pkgName).append(";\n");
221 source.append("public final class $JsCallbacks$ {\n");
222 source.append(" static final $JsCallbacks$ VM = new $JsCallbacks$(null);\n");
223 source.append(" private final org.apidesign.html.boot.spi.Fn.Presenter p;\n");
224 source.append(" private $JsCallbacks$ last;\n");
225 source.append(" private $JsCallbacks$(org.apidesign.html.boot.spi.Fn.Presenter p) {\n");
226 source.append(" this.p = p;\n");
227 source.append(" }\n");
228 source.append(" final $JsCallbacks$ current() {\n");
229 source.append(" org.apidesign.html.boot.spi.Fn.Presenter now = org.apidesign.html.boot.spi.Fn.activePresenter();\n");
230 source.append(" if (now == p) return this;\n");
231 source.append(" if (last != null && now == last.p) return last;\n");
232 source.append(" return last = new $JsCallbacks$(now);\n");
233 source.append(" }\n");
234 for (Map.Entry<String, ExecutableElement> entry : map.entrySet()) {
235 final String mangled = entry.getKey();
236 final ExecutableElement m = entry.getValue();
237 final boolean isStatic = m.getModifiers().contains(Modifier.STATIC);
239 source.append("\n public java.lang.Object ")
245 source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
246 source.append(" self");
251 for (VariableElement ve : m.getParameters()) {
253 source.append(ve.asType());
254 source.append(" arg").append(++cnt);
257 source.append(") throws Throwable {\n");
258 source.append(" try (java.io.Closeable a = org.apidesign.html.boot.spi.Fn.activate(p)) { \n");
260 if (m.getReturnType().getKind() != TypeKind.VOID) {
261 source.append("return ");
264 source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
267 source.append("self.");
269 source.append(m.getSimpleName());
273 for (VariableElement ve : m.getParameters()) {
275 source.append("arg").append(++cnt);
278 source.append(");\n");
279 if (m.getReturnType().getKind() == TypeKind.VOID) {
280 source.append(" return null;\n");
282 source.append(" }\n");
283 source.append(" }\n");
285 source.append("}\n");
286 final String srcName = pkgName + ".$JsCallbacks$";
288 Writer w = processingEnv.getFiler().createSourceFile(srcName,
289 map.values().toArray(new Element[map.size()])
291 w.write(source.toString());
293 } catch (IOException ex) {
294 processingEnv.getMessager().printMessage(
295 Diagnostic.Kind.ERROR, "Can't write " + srcName + ": " + ex.getMessage()
301 private static String findPkg(Element e) {
302 while (e.getKind() != ElementKind.PACKAGE) {
303 e = e.getEnclosingElement();
305 return ((PackageElement)e).getQualifiedName().toString();