2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 1997-2010 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-2013 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.Writer;
47 import java.util.Collections;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.List;
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;
82 * @author Jaroslav Tulach <jtulach@netbeans.org>
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>>();
90 public Set<String> getSupportedAnnotationTypes() {
91 Set<String> set = new HashSet<String>();
92 set.add(JavaScriptBody.class.getName());
93 set.add(JavaScriptResource.class.getName());
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) {
104 ExecutableElement ee = (ExecutableElement)e;
105 List<? extends VariableElement> params = ee.getParameters();
107 JavaScriptBody jsb = e.getAnnotation(JavaScriptBody.class);
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);
115 if (!jsb.javacall() && jsb.body().contains(".@")) {
116 msg.printMessage(Diagnostic.Kind.WARNING, "Usage of .@ usually requires javacall=true", e);
118 if (jsb.javacall()) {
119 JsCallback verify = new VerifyCallback(e);
121 verify.parse(jsb.body());
122 } catch (IllegalStateException ex) {
123 msg.printMessage(Diagnostic.Kind.ERROR, ex.getLocalizedMessage(), e);
127 for (Element e : roundEnv.getElementsAnnotatedWith(JavaScriptResource.class)) {
128 JavaScriptResource r = e.getAnnotation(JavaScriptResource.class);
133 if (r.value().startsWith("/")) {
136 res = findPkg(e).replace('.', '/') + "/" + r.value();
140 FileObject os = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", res);
141 os.openInputStream().close();
142 } catch (IOException ex1) {
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);
152 if (roundEnv.processingOver()) {
153 generateCallbackClass(javacalls);
160 public Iterable<? extends Completion> getCompletions(Element e,
161 AnnotationMirror annotation, ExecutableElement member, String userText
163 StringBuilder sb = new StringBuilder();
164 if (e.getKind() == ElementKind.METHOD && member.getSimpleName().contentEquals("args")) {
165 ExecutableElement ee = (ExecutableElement) e;
168 for (VariableElement ve : ee.getParameters()) {
169 sb.append(sep).append('"').append(ve.getSimpleName())
174 return Collections.nCopies(1, Completions.of(sb.toString()));
179 private class VerifyCallback extends JsCallback {
180 private final Element e;
181 public VerifyCallback(Element e) {
186 protected CharSequence callMethod(String ident, String fqn, String method, String params) {
187 final TypeElement type = processingEnv.getElementUtils().getTypeElement(fqn);
189 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
190 "Callback to non-existing class " + fqn, e
194 ExecutableElement found = null;
195 StringBuilder foundParams = new StringBuilder();
196 for (Element m : type.getEnclosedElements()) {
197 if (m.getKind() != ElementKind.METHOD) {
200 if (m.getSimpleName().contentEquals(method)) {
201 String paramTypes = findParamTypes((ExecutableElement)m);
202 if (paramTypes.equals(params)) {
203 found = (ExecutableElement) m;
206 foundParams.append(paramTypes).append("\n");
210 if (foundParams.length() == 0) {
211 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
212 "Callback to class " + fqn + " with unknown method " + method, e
215 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
216 "Callback to " + fqn + "." + method + " with wrong parameters: " +
217 params + ". Only known parameters are " + foundParams, e
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);
226 String mangled = JsCallback.mangle(fqn, method, findParamTypes(found));
227 mangledOnes.put(mangled, found);
232 private String findParamTypes(ExecutableElement method) {
233 ExecutableType t = (ExecutableType) method.asType();
234 StringBuilder sb = new StringBuilder();
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;
248 throw new IllegalStateException("Uknown " + tm.getKind());
251 while (tm.getKind() == TypeKind.ARRAY) {
253 tm = ((ArrayType)tm).getComponentType();
256 sb.append(tm.toString().replace('.', '/'));
261 return sb.toString();
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);
289 source.append("\n public java.lang.Object ")
295 source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
296 source.append(" self");
301 for (VariableElement ve : m.getParameters()) {
303 source.append(ve.asType());
304 source.append(" arg").append(++cnt);
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");
311 source.append(" java.io.Closeable a = org.apidesign.html.boot.spi.Fn.activate(p); try {\n");
314 if (m.getReturnType().getKind() != TypeKind.VOID) {
315 source.append("return ");
318 source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
321 source.append("self.");
323 source.append(m.getSimpleName());
327 for (VariableElement ve : m.getParameters()) {
329 source.append("arg").append(++cnt);
332 source.append(");\n");
333 if (m.getReturnType().getKind() == TypeKind.VOID) {
334 source.append(" return null;\n");
336 if (processingEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_7) >= 0) {
337 source.append(" }\n");
340 source.append(" } finally {\n");
341 source.append(" a.close();\n");
342 source.append(" }\n");
344 source.append(" }\n");
346 source.append("}\n");
347 final String srcName = pkgName + ".$JsCallbacks$";
349 Writer w = processingEnv.getFiler().createSourceFile(srcName,
350 map.values().toArray(new Element[map.size()])
352 w.write(source.toString());
354 } catch (IOException ex) {
355 processingEnv.getMessager().printMessage(
356 Diagnostic.Kind.ERROR, "Can't write " + srcName + ": " + ex.getMessage()
362 private static String findPkg(Element e) {
363 while (e.getKind() != ElementKind.PACKAGE) {
364 e = e.getEnclosingElement();
366 return ((PackageElement)e).getQualifiedName().toString();