Merge with default branch ios
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 18 Jul 2013 15:39:56 +0200
branchios
changeset 2033c6d52ddc8ce
parent 170 cfec20061d13
parent 202 5a50f193bdff
child 205 c89e6bdb0703
Merge with default branch
     1.1 --- a/boot-fx/pom.xml	Sat Jun 29 07:01:58 2013 +0200
     1.2 +++ b/boot-fx/pom.xml	Thu Jul 18 15:39:56 2013 +0200
     1.3 @@ -35,5 +35,11 @@
     1.4        <version>${project.version}</version>
     1.5        <type>jar</type>
     1.6      </dependency>
     1.7 +    <dependency>
     1.8 +      <groupId>org.testng</groupId>
     1.9 +      <artifactId>testng</artifactId>
    1.10 +      <scope>test</scope>
    1.11 +      <type>jar</type>
    1.12 +    </dependency>
    1.13    </dependencies>
    1.14  </project>
     2.1 --- a/boot-fx/src/main/java/org/apidesign/html/boot/fx/FXPresenter.java	Sat Jun 29 07:01:58 2013 +0200
     2.2 +++ b/boot-fx/src/main/java/org/apidesign/html/boot/fx/FXPresenter.java	Thu Jul 18 15:39:56 2013 +0200
     2.3 @@ -44,7 +44,6 @@
     2.4  import javafx.scene.Scene;
     2.5  import javafx.scene.control.Button;
     2.6  import javafx.scene.layout.BorderPane;
     2.7 -import javafx.scene.layout.HBox;
     2.8  import javafx.scene.layout.VBox;
     2.9  import javafx.scene.text.Text;
    2.10  import javafx.scene.web.WebEngine;
    2.11 @@ -208,14 +207,7 @@
    2.12                  FXBrwsr.class.notifyAll();
    2.13              }
    2.14              BorderPane r = new BorderPane();
    2.15 -            final boolean showToolbar = "true".equals(this.getParameters().getNamed().get("toolbar")); // NOI18N
    2.16 -            final boolean useFirebug = "true".equals(this.getParameters().getNamed().get("firebug")); // NOI18N
    2.17 -            if (showToolbar) {
    2.18 -                //final ToolBar toolbar = new BrowserToolbar(view, vbox, useFirebug, wc.dbg);
    2.19 -                //root.setTop(toolbar);
    2.20 -            }
    2.21              Scene scene = new Scene(r, 800, 600);
    2.22 -            primaryStage.setTitle("Device Emulator");
    2.23              primaryStage.setScene(scene);
    2.24              primaryStage.show();
    2.25              this.root = r;
    2.26 @@ -225,17 +217,7 @@
    2.27              final WebView view = new WebView();
    2.28              final String nbUserDir = this.getParameters().getNamed().get("userdir"); // NOI18N
    2.29              WebController wc = new WebController(view, nbUserDir, getParameters().getUnnamed());
    2.30 -
    2.31 -            final VBox vbox = new VBox();
    2.32 -            vbox.setAlignment(Pos.CENTER);
    2.33 -            vbox.setStyle("-fx-background-color: #808080;");
    2.34 -
    2.35 -            HBox hbox = new HBox();
    2.36 -            hbox.setStyle("-fx-background-color: #808080;");
    2.37 -            hbox.setAlignment(Pos.CENTER);
    2.38 -            hbox.getChildren().add(vbox);
    2.39 -            vbox.getChildren().add(view);
    2.40 -            root.setCenter(hbox);
    2.41 +            root.setCenter(view);
    2.42  
    2.43              final Worker<Void> w = view.getEngine().getLoadWorker();
    2.44              w.stateProperty().addListener(new ChangeListener<Worker.State>() {
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/boot-fx/src/test/java/org/apidesign/html/boot/fx/BootstrapTest.java	Thu Jul 18 15:39:56 2013 +0200
     3.3 @@ -0,0 +1,84 @@
     3.4 +/**
     3.5 + * HTML via Java(tm) Language Bindings
     3.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     3.7 + *
     3.8 + * This program is free software: you can redistribute it and/or modify
     3.9 + * it under the terms of the GNU General Public License as published by
    3.10 + * the Free Software Foundation, version 2 of the License.
    3.11 + *
    3.12 + * This program is distributed in the hope that it will be useful,
    3.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.15 + * GNU General Public License for more details. apidesign.org
    3.16 + * designates this particular file as subject to the
    3.17 + * "Classpath" exception as provided by apidesign.org
    3.18 + * in the License file that accompanied this code.
    3.19 + *
    3.20 + * You should have received a copy of the GNU General Public License
    3.21 + * along with this program. Look for COPYING file in the top folder.
    3.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    3.23 + */
    3.24 +package org.apidesign.html.boot.fx;
    3.25 +
    3.26 +import java.lang.annotation.Annotation;
    3.27 +import java.lang.reflect.Method;
    3.28 +import java.util.ArrayList;
    3.29 +import java.util.List;
    3.30 +import java.util.concurrent.Executors;
    3.31 +import net.java.html.boot.BrowserBuilder;
    3.32 +import static org.testng.Assert.*;
    3.33 +import org.testng.annotations.Factory;
    3.34 +import org.testng.annotations.Test;
    3.35 +
    3.36 +/**
    3.37 + *
    3.38 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    3.39 + */
    3.40 +public class BootstrapTest {
    3.41 +    private static Class<?> browserClass;
    3.42 +    
    3.43 +    public BootstrapTest() {
    3.44 +    }
    3.45 +
    3.46 +    @Factory public static Object[] compatibilityTests() throws Exception {
    3.47 +        final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(BootstrapTest.class).
    3.48 +            loadPage("empty.html").
    3.49 +            invoke("initialized");
    3.50 +
    3.51 +        Executors.newSingleThreadExecutor().submit(new Runnable() {
    3.52 +            @Override
    3.53 +            public void run() {
    3.54 +                bb.showAndWait();
    3.55 +            }
    3.56 +        });
    3.57 +
    3.58 +        List<Object> res = new ArrayList<Object>();
    3.59 +        Class<? extends Annotation> test = 
    3.60 +            loadClass().getClassLoader().loadClass(Test.class.getName()).
    3.61 +            asSubclass(Annotation.class);
    3.62 +        for (Method m : loadClass().getMethods()) {
    3.63 +            if (m.getAnnotation(test) != null) {
    3.64 +                res.add(new KOFx(m));
    3.65 +            }
    3.66 +        }
    3.67 +        return res.toArray();
    3.68 +    }
    3.69 +
    3.70 +    static synchronized Class<?> loadClass() throws InterruptedException {
    3.71 +        while (browserClass == null) {
    3.72 +            BootstrapTest.class.wait();
    3.73 +        }
    3.74 +        return browserClass;
    3.75 +    }
    3.76 +    
    3.77 +    public static synchronized void ready(Class<?> browserCls) throws Exception {
    3.78 +        browserClass = browserCls;
    3.79 +        BootstrapTest.class.notifyAll();
    3.80 +    }
    3.81 +    
    3.82 +    public static void initialized() throws Exception {
    3.83 +        Class<?> classpathClass = ClassLoader.getSystemClassLoader().loadClass(BootstrapTest.class.getName());
    3.84 +        Method m = classpathClass.getMethod("ready", Class.class);
    3.85 +        m.invoke(null, FXPresenterTst.class);
    3.86 +    }
    3.87 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/boot-fx/src/test/java/org/apidesign/html/boot/fx/FXPresenterTst.java	Thu Jul 18 15:39:56 2013 +0200
     4.3 @@ -0,0 +1,49 @@
     4.4 +/**
     4.5 + * HTML via Java(tm) Language Bindings
     4.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4.7 + *
     4.8 + * This program is free software: you can redistribute it and/or modify
     4.9 + * it under the terms of the GNU General Public License as published by
    4.10 + * the Free Software Foundation, version 2 of the License.
    4.11 + *
    4.12 + * This program is distributed in the hope that it will be useful,
    4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.15 + * GNU General Public License for more details. apidesign.org
    4.16 + * designates this particular file as subject to the
    4.17 + * "Classpath" exception as provided by apidesign.org
    4.18 + * in the License file that accompanied this code.
    4.19 + *
    4.20 + * You should have received a copy of the GNU General Public License
    4.21 + * along with this program. Look for COPYING file in the top folder.
    4.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    4.23 + */
    4.24 +package org.apidesign.html.boot.fx;
    4.25 +
    4.26 +import net.java.html.js.JavaScriptBody;
    4.27 +import static org.testng.Assert.*;
    4.28 +import org.testng.annotations.Test;
    4.29 +
    4.30 +/**
    4.31 + *
    4.32 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    4.33 + */
    4.34 +public class FXPresenterTst {
    4.35 +    @Test public void showClassLoader() {
    4.36 +        R run = new R();
    4.37 +        callback(run);
    4.38 +        assertEquals(run.cnt, 1, "Can call even private implementation classes");
    4.39 +    }
    4.40 +    
    4.41 +    @JavaScriptBody(args = { "r" }, javacall = true, body = "r.@java.lang.Runnable::run()();")
    4.42 +    private static native void callback(Runnable r);
    4.43 +
    4.44 +    private static class R implements Runnable {
    4.45 +        int cnt;
    4.46 +
    4.47 +        @Override
    4.48 +        public void run() {
    4.49 +            cnt++;
    4.50 +        }
    4.51 +    }
    4.52 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/boot-fx/src/test/java/org/apidesign/html/boot/fx/KOFx.java	Thu Jul 18 15:39:56 2013 +0200
     5.3 @@ -0,0 +1,97 @@
     5.4 +/**
     5.5 + * HTML via Java(tm) Language Bindings
     5.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     5.7 + *
     5.8 + * This program is free software: you can redistribute it and/or modify
     5.9 + * it under the terms of the GNU General Public License as published by
    5.10 + * the Free Software Foundation, version 2 of the License.
    5.11 + *
    5.12 + * This program is distributed in the hope that it will be useful,
    5.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    5.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    5.15 + * GNU General Public License for more details. apidesign.org
    5.16 + * designates this particular file as subject to the
    5.17 + * "Classpath" exception as provided by apidesign.org
    5.18 + * in the License file that accompanied this code.
    5.19 + *
    5.20 + * You should have received a copy of the GNU General Public License
    5.21 + * along with this program. Look for COPYING file in the top folder.
    5.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    5.23 + */
    5.24 +package org.apidesign.html.boot.fx;
    5.25 +
    5.26 +import java.lang.reflect.InvocationTargetException;
    5.27 +import java.lang.reflect.Method;
    5.28 +import javafx.application.Platform;
    5.29 +import org.testng.IHookCallBack;
    5.30 +import org.testng.IHookable;
    5.31 +import org.testng.ITest;
    5.32 +import org.testng.ITestResult;
    5.33 +import org.testng.annotations.Test;
    5.34 +
    5.35 +/**
    5.36 + *
    5.37 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    5.38 + */
    5.39 +public final class KOFx implements ITest, IHookable, Runnable {
    5.40 +    private final Method m;
    5.41 +    private Object result;
    5.42 +    private Object inst;
    5.43 +
    5.44 +    KOFx(Method m) {
    5.45 +        this.m = m;
    5.46 +    }
    5.47 +
    5.48 +    @Override
    5.49 +    public String getTestName() {
    5.50 +        return m.getName();
    5.51 +    }
    5.52 +
    5.53 +    @Test
    5.54 +    public synchronized void executeTest() throws Exception {
    5.55 +        if (result == null) {
    5.56 +            Platform.runLater(this);
    5.57 +            wait();
    5.58 +        }
    5.59 +        if (result instanceof Exception) {
    5.60 +            throw (Exception)result;
    5.61 +        }
    5.62 +        if (result instanceof Error) {
    5.63 +            throw (Error)result;
    5.64 +        }
    5.65 +    }
    5.66 +
    5.67 +    @Override
    5.68 +    public synchronized void run() {
    5.69 +        boolean notify = true;
    5.70 +        try {
    5.71 +            if (inst == null) {
    5.72 +                inst = m.getDeclaringClass().newInstance();
    5.73 +            }
    5.74 +            result = m.invoke(inst);
    5.75 +            if (result == null) {
    5.76 +                result = this;
    5.77 +            }
    5.78 +        } catch (InvocationTargetException ex) {
    5.79 +            Throwable r = ex.getTargetException();
    5.80 +            if (r instanceof InterruptedException) {
    5.81 +                notify = false;
    5.82 +                Platform.runLater(this);
    5.83 +                return;
    5.84 +            }
    5.85 +            result = r;
    5.86 +        } catch (Exception ex) {
    5.87 +            result = ex;
    5.88 +        } finally {
    5.89 +            if (notify) {
    5.90 +                notifyAll();
    5.91 +            }
    5.92 +        }
    5.93 +    }
    5.94 +
    5.95 +    @Override
    5.96 +    public void run(IHookCallBack ihcb, ITestResult itr) {
    5.97 +        ihcb.runTestMethod(itr);
    5.98 +    }
    5.99 +    
   5.100 +}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/boot-fx/src/test/resources/org/apidesign/html/boot/fx/empty.html	Thu Jul 18 15:39:56 2013 +0200
     6.3 @@ -0,0 +1,33 @@
     6.4 +<!--
     6.5 +
     6.6 +    HTML via Java(tm) Language Bindings
     6.7 +    Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     6.8 +
     6.9 +    This program is free software: you can redistribute it and/or modify
    6.10 +    it under the terms of the GNU General Public License as published by
    6.11 +    the Free Software Foundation, version 2 of the License.
    6.12 +
    6.13 +    This program is distributed in the hope that it will be useful,
    6.14 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
    6.15 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    6.16 +    GNU General Public License for more details. apidesign.org
    6.17 +    designates this particular file as subject to the
    6.18 +    "Classpath" exception as provided by apidesign.org
    6.19 +    in the License file that accompanied this code.
    6.20 +
    6.21 +    You should have received a copy of the GNU General Public License
    6.22 +    along with this program. Look for COPYING file in the top folder.
    6.23 +    If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    6.24 +
    6.25 +-->
    6.26 +<!DOCTYPE html>
    6.27 +<html>
    6.28 +    <head>
    6.29 +        <title>FX Presenter Harness</title>
    6.30 +        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    6.31 +        <meta name="viewport" content="width=device-width">
    6.32 +    </head>
    6.33 +    <body>
    6.34 +        <div>FX Presenter Harness</div>
    6.35 +    </body>
    6.36 +</html>
     7.1 --- a/boot/src/main/java/net/java/html/boot/BrowserBuilder.java	Sat Jun 29 07:01:58 2013 +0200
     7.2 +++ b/boot/src/main/java/net/java/html/boot/BrowserBuilder.java	Thu Jul 18 15:39:56 2013 +0200
     7.3 @@ -20,6 +20,7 @@
     7.4   */
     7.5  package net.java.html.boot;
     7.6  
     7.7 +import java.io.File;
     7.8  import java.io.IOException;
     7.9  import java.lang.reflect.Method;
    7.10  import java.net.MalformedURLException;
    7.11 @@ -103,8 +104,14 @@
    7.12  
    7.13      /** Page to load into the browser. If the <code>page</code> represents
    7.14       * a {@link URL} known to the Java system, the URL is passed to the browser. 
    7.15 -     * Otherwise the system seeks for the resource via {@link Class#getResource(java.lang.String)}
    7.16 -     * method.
    7.17 +     * If system property <code>browser.rootdir</code> is specified, then a
    7.18 +     * file <code>page</code> relative to this directory is used as the URL.
    7.19 +     * If no such file exists, the system seeks for the 
    7.20 +     * resource via {@link Class#getResource(java.lang.String)}
    7.21 +     * method (relative to the {@link #loadClass(java.lang.Class) specified class}). 
    7.22 +     * If such resource is not found, a file relative to the location JAR
    7.23 +     * that contains the {@link #loadClass(java.lang.Class) main class} is 
    7.24 +     * searched for.
    7.25       * 
    7.26       * @param page the location (relative, absolute, or URL) of a page to load
    7.27       * @return this browser
    7.28 @@ -146,7 +153,12 @@
    7.29          URL url = null;
    7.30          MalformedURLException mal = null;
    7.31          try {
    7.32 -            url = new URL(resource);
    7.33 +            String baseURL = System.getProperty("browser.rootdir");
    7.34 +            if (baseURL != null) {
    7.35 +                url = new File(baseURL, resource).toURI().toURL();
    7.36 +            } else {
    7.37 +                url = new URL(resource);
    7.38 +            }
    7.39          } catch (MalformedURLException ex) {
    7.40              mal = ex;
    7.41          }
    7.42 @@ -154,6 +166,15 @@
    7.43              url = clazz.getResource(resource);
    7.44          }
    7.45          if (url == null) {
    7.46 +            URL jar = clazz.getProtectionDomain().getCodeSource().getLocation();
    7.47 +            try {
    7.48 +                url = new URL(jar, resource);
    7.49 +            } catch (MalformedURLException ex) {
    7.50 +                ex.initCause(mal);
    7.51 +                mal = ex;
    7.52 +            }
    7.53 +        }
    7.54 +        if (url == null) {
    7.55              IllegalStateException ise = new IllegalStateException("Can't find resouce: " + resource + " relative to " + clazz);
    7.56              if (mal != null) {
    7.57                  ise.initCause(mal);
     8.1 --- a/boot/src/main/java/net/java/html/js/JavaScriptBody.java	Sat Jun 29 07:01:58 2013 +0200
     8.2 +++ b/boot/src/main/java/net/java/html/js/JavaScriptBody.java	Thu Jul 18 15:39:56 2013 +0200
     8.3 @@ -59,6 +59,8 @@
     8.4       * This is the syntax one can use to call <code>run()</code> 
     8.5       * method of {@link Runnable}:
     8.6       * <pre>r.@java.lang.Runnable::run()()</pre>.
     8.7 +     * One can also call static methods. Just use:
     8.8 +     * <pre>var ten = @java.lang.Integer::parseInt(Ljava/lang/String;)("10")</pre>
     8.9       * 
    8.10       * @return true, if the script should be scanned for special callback
    8.11       *   syntax
     9.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java	Sat Jun 29 07:01:58 2013 +0200
     9.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java	Thu Jul 18 15:39:56 2013 +0200
     9.3 @@ -23,14 +23,12 @@
     9.4  import java.io.InputStream;
     9.5  import java.io.InputStreamReader;
     9.6  import java.io.Reader;
     9.7 -import java.lang.reflect.Method;
     9.8  import java.net.URL;
     9.9  import java.util.ArrayList;
    9.10  import java.util.Collections;
    9.11  import java.util.Enumeration;
    9.12  import java.util.List;
    9.13  import org.apidesign.html.boot.spi.Fn;
    9.14 -import org.objectweb.asm.Type;
    9.15  
    9.16  /**
    9.17   *
    9.18 @@ -76,82 +74,22 @@
    9.19          };
    9.20      }
    9.21  
    9.22 -    static String callback(String body, ClassLoader loader) {
    9.23 -        try {
    9.24 -            return callbackImpl(body, loader);
    9.25 -        } catch (ClassNotFoundException ex) {
    9.26 -            throw new IllegalStateException("Can't parse " + body, ex);
    9.27 -        } catch (NoSuchMethodException ex) {
    9.28 -            throw new IllegalStateException("Can't parse " + body, ex);
    9.29 -        }
    9.30 -    }
    9.31 -    
    9.32 -    private static String callbackImpl(String body, ClassLoader loader)
    9.33 -    throws ClassNotFoundException, NoSuchMethodException {
    9.34 -        StringBuilder sb = new StringBuilder();
    9.35 -        int pos = 0;
    9.36 -        for (;;) {
    9.37 -            int next = body.indexOf(".@", pos);
    9.38 -            if (next == -1) {
    9.39 -                sb.append(body.substring(pos));
    9.40 -                return sb.toString();
    9.41 +    static String callback(final String body) {
    9.42 +        return new JsCallback() {
    9.43 +            @Override
    9.44 +            protected CharSequence callMethod(
    9.45 +                String ident, String fqn, String method, String params
    9.46 +            ) {
    9.47 +                StringBuilder sb = new StringBuilder();
    9.48 +                sb.append("vm.").append(mangle(fqn, method, params));
    9.49 +                sb.append("(");
    9.50 +                if (ident != null) {
    9.51 +                    sb.append(ident);
    9.52 +                }
    9.53 +                return sb;
    9.54              }
    9.55 -            sb.append(body.substring(pos, next));
    9.56 -            
    9.57 -            int sigBeg = body.indexOf('(', next);
    9.58 -            int sigEnd = body.indexOf(')', sigBeg);
    9.59 -            
    9.60 -            int colon4 = body.indexOf("::", next);
    9.61 -            
    9.62 -            if (sigBeg == -1 || sigEnd == -1 || colon4 == -1) {
    9.63 -                throw new IllegalStateException("Malformed body " + body);
    9.64 -            }
    9.65 -            
    9.66 -            String fqn = body.substring(next + 2, colon4);
    9.67 -            String method = body.substring(colon4 + 2, sigBeg);
    9.68 -            String params = body.substring(sigBeg, sigEnd + 1);
    9.69 -            
    9.70 -            Class<?> clazz = Class.forName(fqn, false, loader);
    9.71 -            final Type[] argTps = Type.getArgumentTypes(params);
    9.72 -            Class<?>[] argCls = new Class<?>[argTps.length];
    9.73 -            for (int i = 0; i < argCls.length; i++) {
    9.74 -                argCls[i] = toClass(argTps[i], loader);
    9.75 -            }
    9.76 -            Method m = clazz.getMethod(method, argCls);
    9.77 -            
    9.78 -            sb.append("['").append(m.getName()).append("(");
    9.79 -            String sep = "";
    9.80 -            for (Class<?> pt : m.getParameterTypes()) {
    9.81 -                sb.append(sep).append(pt.getName());
    9.82 -                sep = ",";
    9.83 -            }
    9.84 -            sb.append(")']");
    9.85 -            
    9.86 -            pos = sigEnd + 1;
    9.87 -        }
    9.88 -    }
    9.89  
    9.90 -    private static Class<?> toClass(final Type t, ClassLoader loader) throws ClassNotFoundException {
    9.91 -        if (t == Type.INT_TYPE) {
    9.92 -            return Integer.TYPE;
    9.93 -        } else if (t == Type.VOID_TYPE) {
    9.94 -            return Void.TYPE;
    9.95 -        } else if (t == Type.BOOLEAN_TYPE) {
    9.96 -            return Boolean.TYPE;
    9.97 -        } else if (t == Type.BYTE_TYPE) {
    9.98 -            return Byte.TYPE;
    9.99 -        } else if (t == Type.CHAR_TYPE) {
   9.100 -            return Character.TYPE;
   9.101 -        } else if (t == Type.SHORT_TYPE) {
   9.102 -            return Short.TYPE;
   9.103 -        } else if (t == Type.DOUBLE_TYPE) {
   9.104 -            return Double.TYPE;
   9.105 -        } else if (t == Type.FLOAT_TYPE) {
   9.106 -            return Float.TYPE;
   9.107 -        } else if (t == Type.LONG_TYPE) {
   9.108 -            return Long.TYPE;
   9.109 -        }
   9.110 -        return Class.forName(t.getClassName(), false, loader);
   9.111 +        }.parse(body);
   9.112      }
   9.113  
   9.114      static void loadScript(JsClassLoader jcl, String resource) {
    10.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/JavaScriptProcesor.java	Sat Jun 29 07:01:58 2013 +0200
    10.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JavaScriptProcesor.java	Thu Jul 18 15:39:56 2013 +0200
    10.3 @@ -20,10 +20,15 @@
    10.4   */
    10.5  package org.apidesign.html.boot.impl;
    10.6  
    10.7 +import java.io.IOException;
    10.8 +import java.io.Writer;
    10.9  import java.util.Collections;
   10.10 +import java.util.HashMap;
   10.11  import java.util.HashSet;
   10.12  import java.util.List;
   10.13 +import java.util.Map;
   10.14  import java.util.Set;
   10.15 +import java.util.TreeMap;
   10.16  import javax.annotation.processing.AbstractProcessor;
   10.17  import javax.annotation.processing.Completion;
   10.18  import javax.annotation.processing.Completions;
   10.19 @@ -34,8 +39,14 @@
   10.20  import javax.lang.model.element.Element;
   10.21  import javax.lang.model.element.ElementKind;
   10.22  import javax.lang.model.element.ExecutableElement;
   10.23 +import javax.lang.model.element.Modifier;
   10.24 +import javax.lang.model.element.PackageElement;
   10.25  import javax.lang.model.element.TypeElement;
   10.26  import javax.lang.model.element.VariableElement;
   10.27 +import javax.lang.model.type.ArrayType;
   10.28 +import javax.lang.model.type.ExecutableType;
   10.29 +import javax.lang.model.type.TypeKind;
   10.30 +import javax.lang.model.type.TypeMirror;
   10.31  import javax.tools.Diagnostic;
   10.32  import net.java.html.js.JavaScriptBody;
   10.33  import net.java.html.js.JavaScriptResource;
   10.34 @@ -47,6 +58,9 @@
   10.35   */
   10.36  @ServiceProvider(service = Processor.class)
   10.37  public final class JavaScriptProcesor extends AbstractProcessor {
   10.38 +    private final Map<String,Map<String,ExecutableElement>> javacalls = 
   10.39 +        new HashMap<String,Map<String,ExecutableElement>>();
   10.40 +    
   10.41      @Override
   10.42      public Set<String> getSupportedAnnotationTypes() {
   10.43          Set<String> set = new HashSet<String>();
   10.44 @@ -76,6 +90,14 @@
   10.45              if (!jsb.javacall() && jsb.body().contains(".@")) {
   10.46                  msg.printMessage(Diagnostic.Kind.WARNING, "Usage of .@ usually requires javacall=true", e);
   10.47              }
   10.48 +            if (jsb.javacall()) {
   10.49 +                JsCallback verify = new VerifyCallback(e);
   10.50 +                verify.parse(jsb.body());
   10.51 +            }
   10.52 +        }
   10.53 +        if (roundEnv.processingOver()) {
   10.54 +            generateCallbackClass(javacalls);
   10.55 +            javacalls.clear();
   10.56          }
   10.57          return true;
   10.58      }
   10.59 @@ -100,5 +122,178 @@
   10.60          return null;
   10.61      }
   10.62  
   10.63 +    private class VerifyCallback extends JsCallback {
   10.64 +        private final Element e;
   10.65 +        public VerifyCallback(Element e) {
   10.66 +            this.e = e;
   10.67 +        }
   10.68 +
   10.69 +        @Override
   10.70 +        protected CharSequence callMethod(String ident, String fqn, String method, String params) {
   10.71 +            final TypeElement type = processingEnv.getElementUtils().getTypeElement(fqn);
   10.72 +            if (type == null) {
   10.73 +                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   10.74 +                    "Callback to non-existing class " + fqn, e
   10.75 +                );
   10.76 +                return "";
   10.77 +            }
   10.78 +            ExecutableElement found = null;
   10.79 +            StringBuilder foundParams = new StringBuilder();
   10.80 +            for (Element m : type.getEnclosedElements()) {
   10.81 +                if (m.getKind() != ElementKind.METHOD) {
   10.82 +                    continue;
   10.83 +                }
   10.84 +                if (m.getSimpleName().contentEquals(method)) {
   10.85 +                    String paramTypes = findParamTypes((ExecutableElement)m);
   10.86 +                    if (paramTypes.equals(params)) {
   10.87 +                        found = (ExecutableElement) m;
   10.88 +                        break;
   10.89 +                    }
   10.90 +                    foundParams.append(paramTypes).append("\n");
   10.91 +                }
   10.92 +            }
   10.93 +            if (found == null) {
   10.94 +                if (foundParams.length() == 0) {
   10.95 +                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
   10.96 +                        "Callback to class " + fqn + " with unknown method " + method, e
   10.97 +                    );
   10.98 +                } else {
   10.99 +                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 
  10.100 +                        "Callback to " + fqn + "." + method + " with wrong parameters: " + 
  10.101 +                        params + ". Only known parameters are " + foundParams, e
  10.102 +                    );
  10.103 +                }
  10.104 +            } else {
  10.105 +                Map<String,ExecutableElement> mangledOnes = javacalls.get(findPkg(e));
  10.106 +                if (mangledOnes == null) {
  10.107 +                    mangledOnes = new TreeMap<String, ExecutableElement>();
  10.108 +                    javacalls.put(findPkg(e), mangledOnes);
  10.109 +                }
  10.110 +                String mangled = JsCallback.mangle(fqn, method, findParamTypes(found));
  10.111 +                mangledOnes.put(mangled, found);
  10.112 +            }
  10.113 +            return "";
  10.114 +        }
  10.115 +
  10.116 +        private String findParamTypes(ExecutableElement method) {
  10.117 +            ExecutableType t = (ExecutableType) method.asType();
  10.118 +            StringBuilder sb = new StringBuilder();
  10.119 +            sb.append('(');
  10.120 +            for (TypeMirror tm : t.getParameterTypes()) {
  10.121 +                if (tm.getKind().isPrimitive()) {
  10.122 +                    switch (tm.getKind()) {
  10.123 +                        case INT: sb.append('I'); break;
  10.124 +                        case BOOLEAN: sb.append('Z'); break;
  10.125 +                        case BYTE: sb.append('B'); break;
  10.126 +                        case CHAR: sb.append('C'); break;
  10.127 +                        case SHORT: sb.append('S'); break;
  10.128 +                        case DOUBLE: sb.append('D'); break;
  10.129 +                        case FLOAT: sb.append('F'); break;
  10.130 +                        case LONG: sb.append('J'); break;
  10.131 +                        default:
  10.132 +                            throw new IllegalStateException("Uknown " + tm.getKind());
  10.133 +                    }
  10.134 +                } else {
  10.135 +                    while (tm.getKind() == TypeKind.ARRAY) {
  10.136 +                        sb.append('[');
  10.137 +                        tm = ((ArrayType)tm).getComponentType();
  10.138 +                    }
  10.139 +                    sb.append('L');
  10.140 +                    sb.append(tm.toString().replace('.', '/'));
  10.141 +                    sb.append(';');
  10.142 +                }
  10.143 +            }
  10.144 +            sb.append(')');
  10.145 +            return sb.toString();
  10.146 +        }
  10.147 +    }
  10.148 +    
  10.149 +    private void generateCallbackClass(Map<String,Map<String, ExecutableElement>> process) {
  10.150 +        for (Map.Entry<String, Map<String, ExecutableElement>> pkgEn : process.entrySet()) {
  10.151 +            String pkgName = pkgEn.getKey();
  10.152 +            Map<String, ExecutableElement> map = pkgEn.getValue();
  10.153 +            StringBuilder source = new StringBuilder();
  10.154 +            source.append("package ").append(pkgName).append(";\n");
  10.155 +            source.append("public final class $JsCallbacks$ {\n");
  10.156 +            source.append("  static final $JsCallbacks$ VM = new $JsCallbacks$();\n");
  10.157 +            source.append("  private $JsCallbacks$() {}\n");
  10.158 +            for (Map.Entry<String, ExecutableElement> entry : map.entrySet()) {
  10.159 +                final String mangled = entry.getKey();
  10.160 +                final ExecutableElement m = entry.getValue();
  10.161 +                final boolean isStatic = m.getModifiers().contains(Modifier.STATIC);
  10.162 +                
  10.163 +                source.append("\n  public java.lang.Object ")
  10.164 +                    .append(mangled)
  10.165 +                    .append("(");
  10.166 +                
  10.167 +                String sep = "";
  10.168 +                if (!isStatic) {
  10.169 +                    source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
  10.170 +                    source.append(" self");
  10.171 +                    sep = ", ";
  10.172 +                }
  10.173 +                
  10.174 +                int cnt = 0;
  10.175 +                for (VariableElement ve : m.getParameters()) {
  10.176 +                    source.append(sep);
  10.177 +                    source.append(ve.asType());
  10.178 +                    source.append(" arg").append(++cnt);
  10.179 +                    sep = ", ";
  10.180 +                }
  10.181 +                source.append(")");
  10.182 +                sep = "\n throws ";
  10.183 +                for (TypeMirror thrwn : m.getThrownTypes()) {
  10.184 +                    source.append(sep).append(thrwn.toString());
  10.185 +                    sep = ",";
  10.186 +                }
  10.187 +                source.append(" {\n");
  10.188 +                source.append("    ");
  10.189 +                if (m.getReturnType().getKind() != TypeKind.VOID) {
  10.190 +                    source.append("return ");
  10.191 +                }
  10.192 +                if (isStatic) {
  10.193 +                    source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
  10.194 +                    source.append('.');
  10.195 +                } else {
  10.196 +                    source.append("self.");
  10.197 +                }
  10.198 +                source.append(m.getSimpleName());
  10.199 +                source.append("(");
  10.200 +                cnt = 0;
  10.201 +                sep = "";
  10.202 +                for (VariableElement ve : m.getParameters()) {
  10.203 +                    source.append(sep);
  10.204 +                    source.append("arg").append(++cnt);
  10.205 +                    sep = ", ";
  10.206 +                }
  10.207 +                source.append(");\n");
  10.208 +                if (m.getReturnType().getKind() == TypeKind.VOID) {
  10.209 +                    source.append("    return null;\n");
  10.210 +                }
  10.211 +                source.append("  }\n");
  10.212 +            }
  10.213 +            source.append("}\n");
  10.214 +            final String srcName = pkgName + ".$JsCallbacks$";
  10.215 +            try {
  10.216 +                Writer w = processingEnv.getFiler().createSourceFile(srcName,
  10.217 +                    map.values().toArray(new Element[map.size()])
  10.218 +                ).openWriter();
  10.219 +                w.write(source.toString());
  10.220 +                w.close();
  10.221 +                return;
  10.222 +            } catch (IOException ex) {
  10.223 +                processingEnv.getMessager().printMessage(
  10.224 +                    Diagnostic.Kind.ERROR, "Can't write " + srcName + ": " + ex.getMessage()
  10.225 +                );
  10.226 +            }
  10.227 +        }
  10.228 +    }
  10.229 +    
  10.230 +    private static String findPkg(Element e) {
  10.231 +        while (e.getKind() != ElementKind.PACKAGE) {
  10.232 +            e = e.getEnclosingElement();
  10.233 +        }
  10.234 +        return ((PackageElement)e).getQualifiedName().toString();
  10.235 +    }
  10.236      
  10.237  }
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsCallback.java	Thu Jul 18 15:39:56 2013 +0200
    11.3 @@ -0,0 +1,126 @@
    11.4 +/**
    11.5 + * HTML via Java(tm) Language Bindings
    11.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    11.7 + *
    11.8 + * This program is free software: you can redistribute it and/or modify
    11.9 + * it under the terms of the GNU General Public License as published by
   11.10 + * the Free Software Foundation, version 2 of the License.
   11.11 + *
   11.12 + * This program is distributed in the hope that it will be useful,
   11.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   11.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   11.15 + * GNU General Public License for more details. apidesign.org
   11.16 + * designates this particular file as subject to the
   11.17 + * "Classpath" exception as provided by apidesign.org
   11.18 + * in the License file that accompanied this code.
   11.19 + *
   11.20 + * You should have received a copy of the GNU General Public License
   11.21 + * along with this program. Look for COPYING file in the top folder.
   11.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   11.23 + */
   11.24 +package org.apidesign.html.boot.impl;
   11.25 +
   11.26 +
   11.27 +/**
   11.28 + *
   11.29 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   11.30 + */
   11.31 +abstract class JsCallback {
   11.32 +    final String parse(String body) {
   11.33 +        StringBuilder sb = new StringBuilder();
   11.34 +        int pos = 0;
   11.35 +        for (;;) {
   11.36 +            int next = body.indexOf(".@", pos);
   11.37 +            if (next == -1) {
   11.38 +                sb.append(body.substring(pos));
   11.39 +                body = sb.toString();
   11.40 +                break;
   11.41 +            }
   11.42 +            int ident = next;
   11.43 +            while (ident > 0) {
   11.44 +                if (!Character.isJavaIdentifierPart(body.charAt(--ident))) {
   11.45 +                    ident++;
   11.46 +                    break;
   11.47 +                }
   11.48 +            }
   11.49 +            String refId = body.substring(ident, next);
   11.50 +            
   11.51 +            sb.append(body.substring(pos, ident));
   11.52 +            
   11.53 +            int sigBeg = body.indexOf('(', next);
   11.54 +            int sigEnd = body.indexOf(')', sigBeg);
   11.55 +            int colon4 = body.indexOf("::", next);
   11.56 +            if (sigBeg == -1 || sigEnd == -1 || colon4 == -1) {
   11.57 +                throw new IllegalStateException("Malformed body " + body);
   11.58 +            }
   11.59 +            String fqn = body.substring(next + 2, colon4);
   11.60 +            String method = body.substring(colon4 + 2, sigBeg);
   11.61 +            String params = body.substring(sigBeg, sigEnd + 1);
   11.62 +
   11.63 +            int paramBeg = body.indexOf('(', sigEnd + 1);
   11.64 +            if (paramBeg == -1) {
   11.65 +                throw new IllegalStateException("Malformed body " + body);
   11.66 +            }
   11.67 +            
   11.68 +            sb.append(callMethod(refId, fqn, method, params));
   11.69 +            if (body.charAt(paramBeg + 1) != (')')) {
   11.70 +                sb.append(",");
   11.71 +            }
   11.72 +            pos = paramBeg + 1;
   11.73 +        }
   11.74 +        pos = 0;
   11.75 +        sb = null;
   11.76 +        for (;;) {
   11.77 +            int next = body.indexOf("@", pos);
   11.78 +            if (next == -1) {
   11.79 +                if (sb == null) {
   11.80 +                    return body;
   11.81 +                }
   11.82 +                sb.append(body.substring(pos));
   11.83 +                return sb.toString();
   11.84 +            }
   11.85 +            if (sb == null) {
   11.86 +                sb = new StringBuilder();
   11.87 +            }
   11.88 +            
   11.89 +            sb.append(body.substring(pos, next));
   11.90 +            
   11.91 +            int sigBeg = body.indexOf('(', next);
   11.92 +            int sigEnd = body.indexOf(')', sigBeg);
   11.93 +            int colon4 = body.indexOf("::", next);
   11.94 +            if (sigBeg == -1 || sigEnd == -1 || colon4 == -1) {
   11.95 +                throw new IllegalStateException("Malformed body " + body);
   11.96 +            }
   11.97 +            String fqn = body.substring(next + 1, colon4);
   11.98 +            String method = body.substring(colon4 + 2, sigBeg);
   11.99 +            String params = body.substring(sigBeg, sigEnd + 1);
  11.100 +
  11.101 +            int paramBeg = body.indexOf('(', sigEnd + 1);
  11.102 +            
  11.103 +            sb.append(callMethod(null, fqn, method, params));
  11.104 +            pos = paramBeg + 1;
  11.105 +        }
  11.106 +    }
  11.107 +
  11.108 +    protected abstract CharSequence callMethod(
  11.109 +        String ident, String fqn, String method, String params
  11.110 +    );
  11.111 +
  11.112 +    static String mangle(String fqn, String method, String params) {
  11.113 +        if (params.startsWith("(")) {
  11.114 +            params = params.substring(1);
  11.115 +        }
  11.116 +        if (params.endsWith(")")) {
  11.117 +            params = params.substring(0, params.length() - 1);
  11.118 +        }
  11.119 +        return 
  11.120 +            replace(fqn) + "$" + replace(method) + "$" + replace(params);
  11.121 +    }
  11.122 +    
  11.123 +    private static String replace(String orig) {
  11.124 +        return orig.replace("_", "_1").
  11.125 +            replace(";", "_2").
  11.126 +            replace("[", "_3").
  11.127 +            replace('.', '_').replace('/', '_');
  11.128 +    }
  11.129 +}
    12.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java	Sat Jun 29 07:01:58 2013 +0200
    12.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java	Thu Jul 18 15:39:56 2013 +0200
    12.3 @@ -218,11 +218,14 @@
    12.4                  super.visitLdcInsn(body);
    12.5                  super.visitIntInsn(Opcodes.SIPUSH, args.size());
    12.6                  super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
    12.7 +                boolean needsVM = false;
    12.8                  for (int i = 0; i < args.size(); i++) {
    12.9 -                    String name = args.get(i);
   12.10 +                    assert !needsVM;
   12.11 +                    String argName = args.get(i);
   12.12 +                    needsVM = "vm".equals(argName);
   12.13                      super.visitInsn(Opcodes.DUP);
   12.14                      super.visitIntInsn(Opcodes.BIPUSH, i);
   12.15 -                    super.visitLdcInsn(name);
   12.16 +                    super.visitLdcInsn(argName);
   12.17                      super.visitInsn(Opcodes.AASTORE);
   12.18                  }
   12.19                  super.visitMethodInsn(Opcodes.INVOKESTATIC, 
   12.20 @@ -249,6 +252,7 @@
   12.21                      private boolean nowReturn;
   12.22                      private Type returnType;
   12.23                      private int index;
   12.24 +                    private int loadIndex = offset;
   12.25                      
   12.26                      public SV() {
   12.27                          super(Opcodes.ASM4);
   12.28 @@ -262,15 +266,15 @@
   12.29                              return;
   12.30                          }
   12.31                          FindInMethod.super.visitInsn(Opcodes.DUP);
   12.32 -                        FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
   12.33 -                        FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), index + offset);
   12.34 +                        FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   12.35 +                        FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
   12.36                          String factory;
   12.37                          switch (descriptor) {
   12.38                          case 'I': factory = "java/lang/Integer"; break;
   12.39 -                        case 'J': factory = "java/lang/Long"; break;
   12.40 +                        case 'J': factory = "java/lang/Long"; loadIndex++; break;
   12.41                          case 'S': factory = "java/lang/Short"; break;
   12.42                          case 'F': factory = "java/lang/Float"; break;
   12.43 -                        case 'D': factory = "java/lang/Double"; break;
   12.44 +                        case 'D': factory = "java/lang/Double"; loadIndex++; break;
   12.45                          case 'Z': factory = "java/lang/Boolean"; break;
   12.46                          case 'C': factory = "java/lang/Character"; break;
   12.47                          case 'B': factory = "java/lang/Byte"; break;
   12.48 @@ -280,7 +284,6 @@
   12.49                              factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
   12.50                          );
   12.51                          FindInMethod.super.visitInsn(Opcodes.AASTORE);
   12.52 -                        index++;
   12.53                      }
   12.54  
   12.55                      @Override
   12.56 @@ -309,10 +312,9 @@
   12.57  
   12.58                      private void loadObject() {
   12.59                          FindInMethod.super.visitInsn(Opcodes.DUP);
   12.60 -                        FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
   12.61 -                        FindInMethod.super.visitVarInsn(Opcodes.ALOAD, index + offset);
   12.62 +                        FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   12.63 +                        FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
   12.64                          FindInMethod.super.visitInsn(Opcodes.AASTORE);
   12.65 -                        index++;
   12.66                      }
   12.67                      
   12.68                  }
   12.69 @@ -320,6 +322,15 @@
   12.70                  SignatureReader sr = new SignatureReader(desc);
   12.71                  sr.accept(sv);
   12.72                  
   12.73 +                if (needsVM) {
   12.74 +                    FindInMethod.super.visitInsn(Opcodes.DUP);
   12.75 +                    FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
   12.76 +                    int lastSlash = FindInClass.this.name.lastIndexOf('/');
   12.77 +                    String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
   12.78 +                    FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
   12.79 +                    FindInMethod.super.visitInsn(Opcodes.AASTORE);
   12.80 +                }
   12.81 +                
   12.82                  super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   12.83                      "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
   12.84                  );
   12.85 @@ -401,10 +412,11 @@
   12.86                  @Override
   12.87                  public void visitEnd() {
   12.88                      if (body != null) {
   12.89 -                        generateJSBody(args, javacall ? 
   12.90 -                            FnUtils.callback(body, JsClassLoader.this) : 
   12.91 -                            body
   12.92 -                        );
   12.93 +                        if (javacall) {
   12.94 +                            body = FnUtils.callback(body);
   12.95 +                            args.add("vm");
   12.96 +                        }
   12.97 +                        generateJSBody(args, body);
   12.98                      }
   12.99                  }
  12.100              }
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/Compile.java	Thu Jul 18 15:39:56 2013 +0200
    13.3 @@ -0,0 +1,269 @@
    13.4 +/**
    13.5 + * HTML via Java(tm) Language Bindings
    13.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    13.7 + *
    13.8 + * This program is free software: you can redistribute it and/or modify
    13.9 + * it under the terms of the GNU General Public License as published by
   13.10 + * the Free Software Foundation, version 2 of the License.
   13.11 + *
   13.12 + * This program is distributed in the hope that it will be useful,
   13.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   13.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13.15 + * GNU General Public License for more details. apidesign.org
   13.16 + * designates this particular file as subject to the
   13.17 + * "Classpath" exception as provided by apidesign.org
   13.18 + * in the License file that accompanied this code.
   13.19 + *
   13.20 + * You should have received a copy of the GNU General Public License
   13.21 + * along with this program. Look for COPYING file in the top folder.
   13.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   13.23 + */
   13.24 +package org.apidesign.html.boot.impl;
   13.25 +
   13.26 +import java.io.ByteArrayInputStream;
   13.27 +import java.io.ByteArrayOutputStream;
   13.28 +import java.io.IOException;
   13.29 +import java.io.InputStream;
   13.30 +import java.io.OutputStream;
   13.31 +import java.net.URI;
   13.32 +import java.net.URISyntaxException;
   13.33 +import java.util.ArrayList;
   13.34 +import java.util.Arrays;
   13.35 +import java.util.HashMap;
   13.36 +import java.util.List;
   13.37 +import java.util.Locale;
   13.38 +import java.util.Map;
   13.39 +import java.util.regex.Matcher;
   13.40 +import java.util.regex.Pattern;
   13.41 +import javax.tools.Diagnostic;
   13.42 +import javax.tools.DiagnosticListener;
   13.43 +import javax.tools.FileObject;
   13.44 +import javax.tools.ForwardingJavaFileManager;
   13.45 +import javax.tools.JavaFileManager;
   13.46 +import javax.tools.JavaFileObject;
   13.47 +import javax.tools.JavaFileObject.Kind;
   13.48 +import javax.tools.SimpleJavaFileObject;
   13.49 +import javax.tools.StandardJavaFileManager;
   13.50 +import javax.tools.StandardLocation;
   13.51 +import javax.tools.ToolProvider;
   13.52 +import static org.testng.Assert.assertTrue;
   13.53 +import static org.testng.Assert.assertFalse;
   13.54 +import static org.testng.Assert.fail;
   13.55 +
   13.56 +/**
   13.57 + *
   13.58 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   13.59 + */
   13.60 +final class Compile implements DiagnosticListener<JavaFileObject> {
   13.61 +    private final List<Diagnostic<? extends JavaFileObject>> errors = 
   13.62 +            new ArrayList<Diagnostic<? extends JavaFileObject>>();
   13.63 +    private final Map<String, byte[]> classes;
   13.64 +    private final String pkg;
   13.65 +    private final String cls;
   13.66 +    private final String html;
   13.67 +    private final String sourceLevel;
   13.68 +
   13.69 +    private Compile(String html, String code, String sl) throws IOException {
   13.70 +        this.pkg = findPkg(code);
   13.71 +        this.cls = findCls(code);
   13.72 +        this.html = html;
   13.73 +        this.sourceLevel = sl;
   13.74 +        classes = compile(html, code);
   13.75 +    }
   13.76 +
   13.77 +    /** Performs compilation of given HTML page and associated Java code
   13.78 +     */
   13.79 +    public static Compile create(String html, String code) throws IOException {
   13.80 +        return create(html, code, "1.7");
   13.81 +    }
   13.82 +    static Compile create(String html, String code, String sourceLevel) throws IOException {
   13.83 +        return new Compile(html, code, sourceLevel);
   13.84 +    }
   13.85 +    
   13.86 +    /** Checks for given class among compiled resources */
   13.87 +    public byte[] get(String res) {
   13.88 +        return classes.get(res);
   13.89 +    }
   13.90 +    
   13.91 +    /** Obtains errors created during compilation.
   13.92 +     */
   13.93 +    public List<Diagnostic<? extends JavaFileObject>> getErrors() {
   13.94 +        List<Diagnostic<? extends JavaFileObject>> err;
   13.95 +        err = new ArrayList<Diagnostic<? extends JavaFileObject>>();
   13.96 +        for (Diagnostic<? extends JavaFileObject> diagnostic : errors) {
   13.97 +            if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
   13.98 +                err.add(diagnostic);
   13.99 +            }
  13.100 +        }
  13.101 +        return err;
  13.102 +    }
  13.103 +    
  13.104 +    private Map<String, byte[]> compile(final String html, final String code) throws IOException {
  13.105 +        StandardJavaFileManager sjfm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(this, null, null);
  13.106 +
  13.107 +        final Map<String, ByteArrayOutputStream> class2BAOS;
  13.108 +        class2BAOS = new HashMap<String, ByteArrayOutputStream>();
  13.109 +
  13.110 +        JavaFileObject file = new SimpleJavaFileObject(URI.create("mem://mem"), Kind.SOURCE) {
  13.111 +            @Override
  13.112 +            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
  13.113 +                return code;
  13.114 +            }
  13.115 +        };
  13.116 +        final JavaFileObject htmlFile = new SimpleJavaFileObject(URI.create("mem://mem2"), Kind.OTHER) {
  13.117 +            @Override
  13.118 +            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
  13.119 +                return html;
  13.120 +            }
  13.121 +
  13.122 +            @Override
  13.123 +            public InputStream openInputStream() throws IOException {
  13.124 +                return new ByteArrayInputStream(html.getBytes());
  13.125 +            }
  13.126 +        };
  13.127 +        
  13.128 +        final URI scratch;
  13.129 +        try {
  13.130 +            scratch = new URI("mem://mem3");
  13.131 +        } catch (URISyntaxException ex) {
  13.132 +            throw new IOException(ex);
  13.133 +        }
  13.134 +        
  13.135 +        JavaFileManager jfm = new ForwardingJavaFileManager<JavaFileManager>(sjfm) {
  13.136 +            @Override
  13.137 +            public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
  13.138 +                if (kind  == Kind.CLASS) {
  13.139 +                    final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  13.140 +
  13.141 +                    class2BAOS.put(className.replace('.', '/') + ".class", buffer);
  13.142 +                    return new SimpleJavaFileObject(sibling.toUri(), kind) {
  13.143 +                        @Override
  13.144 +                        public OutputStream openOutputStream() throws IOException {
  13.145 +                            return buffer;
  13.146 +                        }
  13.147 +                    };
  13.148 +                }
  13.149 +                
  13.150 +                if (kind == Kind.SOURCE) {
  13.151 +                    final String n = className.replace('.', '/') + ".java";
  13.152 +                    final URI un;
  13.153 +                    try {
  13.154 +                        un = new URI("mem://" + n);
  13.155 +                    } catch (URISyntaxException ex) {
  13.156 +                        throw new IOException(ex);
  13.157 +                    }
  13.158 +                    return new VirtFO(un/*sibling.toUri()*/, kind, n);
  13.159 +                }
  13.160 +                
  13.161 +                throw new IllegalStateException();
  13.162 +            }
  13.163 +
  13.164 +            @Override
  13.165 +            public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
  13.166 +                if (location == StandardLocation.SOURCE_PATH) {
  13.167 +                    if (packageName.equals(pkg)) {
  13.168 +                        return htmlFile;
  13.169 +                    }
  13.170 +                }
  13.171 +                
  13.172 +                return null;
  13.173 +            }
  13.174 +
  13.175 +            @Override
  13.176 +            public boolean isSameFile(FileObject a, FileObject b) {
  13.177 +                if (a instanceof VirtFO && b instanceof VirtFO) {
  13.178 +                    return ((VirtFO)a).getName().equals(((VirtFO)b).getName());
  13.179 +                }
  13.180 +                
  13.181 +                return super.isSameFile(a, b);
  13.182 +            }
  13.183 +
  13.184 +            class VirtFO extends SimpleJavaFileObject {
  13.185 +
  13.186 +                private final String n;
  13.187 +
  13.188 +                public VirtFO(URI uri, Kind kind, String n) {
  13.189 +                    super(uri, kind);
  13.190 +                    this.n = n;
  13.191 +                }
  13.192 +                private final ByteArrayOutputStream data = new ByteArrayOutputStream();
  13.193 +
  13.194 +                @Override
  13.195 +                public OutputStream openOutputStream() throws IOException {
  13.196 +                    return data;
  13.197 +                }
  13.198 +
  13.199 +                @Override
  13.200 +                public String getName() {
  13.201 +                    return n;
  13.202 +                }
  13.203 +
  13.204 +                @Override
  13.205 +                public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
  13.206 +                    data.close();
  13.207 +                    return new String(data.toByteArray());
  13.208 +                }
  13.209 +            }
  13.210 +        };
  13.211 +
  13.212 +        ToolProvider.getSystemJavaCompiler().getTask(null, jfm, this, /*XXX:*/Arrays.asList("-source", sourceLevel, "-target", "1.7"), null, Arrays.asList(file)).call();
  13.213 +
  13.214 +        Map<String, byte[]> result = new HashMap<String, byte[]>();
  13.215 +
  13.216 +        for (Map.Entry<String, ByteArrayOutputStream> e : class2BAOS.entrySet()) {
  13.217 +            result.put(e.getKey(), e.getValue().toByteArray());
  13.218 +        }
  13.219 +
  13.220 +        return result;
  13.221 +    }
  13.222 +
  13.223 +
  13.224 +    @Override
  13.225 +    public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
  13.226 +        errors.add(diagnostic);
  13.227 +    }
  13.228 +    private static String findPkg(String java) throws IOException {
  13.229 +        Pattern p = Pattern.compile("package\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}*;", Pattern.MULTILINE);
  13.230 +        Matcher m = p.matcher(java);
  13.231 +        if (!m.find()) {
  13.232 +            throw new IOException("Can't find package declaration in the java file");
  13.233 +        }
  13.234 +        String pkg = m.group(1);
  13.235 +        return pkg;
  13.236 +    }
  13.237 +    private static String findCls(String java) throws IOException {
  13.238 +        Pattern p = Pattern.compile("class\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}", Pattern.MULTILINE);
  13.239 +        Matcher m = p.matcher(java);
  13.240 +        if (!m.find()) {
  13.241 +            throw new IOException("Can't find package declaration in the java file");
  13.242 +        }
  13.243 +        String cls = m.group(1);
  13.244 +        return cls;
  13.245 +    }
  13.246 +
  13.247 +    String getHtml() {
  13.248 +        String fqn = "'" + pkg + '.' + cls + "'";
  13.249 +        return html.replace("'${fqn}'", fqn);
  13.250 +    }
  13.251 +    void assertErrors() {
  13.252 +        assertFalse(getErrors().isEmpty(), "There are supposed to be some errors");
  13.253 +    }
  13.254 +
  13.255 +    void assertError(String expMsg) {
  13.256 +        StringBuilder sb = new StringBuilder();
  13.257 +        sb.append("Can't find ").append(expMsg).append(" among:");
  13.258 +        for (Diagnostic<? extends JavaFileObject> e : errors) {
  13.259 +            String msg = e.getMessage(Locale.US);
  13.260 +            if (msg.contains(expMsg)) {
  13.261 +                return;
  13.262 +            }
  13.263 +            sb.append("\n");
  13.264 +            sb.append(msg);
  13.265 +        }
  13.266 +        fail(sb.toString());
  13.267 +    }
  13.268 +
  13.269 +    void assertNoErrors() {
  13.270 +        assertTrue(getErrors().isEmpty(), "No errors expected: " + getErrors());
  13.271 +    }
  13.272 +}
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/JavaScriptProcesorTest.java	Thu Jul 18 15:39:56 2013 +0200
    14.3 @@ -0,0 +1,107 @@
    14.4 +/**
    14.5 + * HTML via Java(tm) Language Bindings
    14.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    14.7 + *
    14.8 + * This program is free software: you can redistribute it and/or modify
    14.9 + * it under the terms of the GNU General Public License as published by
   14.10 + * the Free Software Foundation, version 2 of the License.
   14.11 + *
   14.12 + * This program is distributed in the hope that it will be useful,
   14.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   14.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14.15 + * GNU General Public License for more details. apidesign.org
   14.16 + * designates this particular file as subject to the
   14.17 + * "Classpath" exception as provided by apidesign.org
   14.18 + * in the License file that accompanied this code.
   14.19 + *
   14.20 + * You should have received a copy of the GNU General Public License
   14.21 + * along with this program. Look for COPYING file in the top folder.
   14.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   14.23 + */
   14.24 +package org.apidesign.html.boot.impl;
   14.25 +
   14.26 +import java.io.IOException;
   14.27 +import java.lang.reflect.Field;
   14.28 +import java.lang.reflect.Method;
   14.29 +import static org.testng.Assert.assertEquals;
   14.30 +import static org.testng.Assert.assertTrue;
   14.31 +import org.testng.annotations.Test;
   14.32 +
   14.33 +/**
   14.34 + *
   14.35 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   14.36 + */
   14.37 +public class JavaScriptProcesorTest {
   14.38 +    
   14.39 +    @Test public void detectCallbackToNonExistingClass() throws IOException {
   14.40 +        String code = "package x.y.z;\n"
   14.41 +            + "import net.java.html.js.JavaScriptBody;\n"
   14.42 +            + "class X {\n"
   14.43 +            + "  @JavaScriptBody(args={\"r\"}, javacall=true, body =\n"
   14.44 +            + "    \"r.@java.lang.Runable::run()();\"\n" // typo
   14.45 +            + "  )\n"
   14.46 +            + "  private static native void callback(Runnable r);\n"
   14.47 +            + "}\n";
   14.48 +        
   14.49 +        Compile c = Compile.create("", code);
   14.50 +        c.assertErrors();
   14.51 +        c.assertError("java.lang.Runable"); // typo
   14.52 +    }
   14.53 +
   14.54 +    @Test public void detectCallbackToNonExistingMethod() throws IOException {
   14.55 +        String code = "package x.y.z;\n"
   14.56 +            + "import net.java.html.js.JavaScriptBody;\n"
   14.57 +            + "class X {\n"
   14.58 +            + "  @JavaScriptBody(args={\"r\"}, javacall=true, body =\n"
   14.59 +            + "    \"r.@java.lang.Runnable::cancel()();\"\n"
   14.60 +            + "  )\n"
   14.61 +            + "  private static native void callback(Runnable r);\n"
   14.62 +            + "}\n";
   14.63 +        
   14.64 +        Compile c = Compile.create("", code);
   14.65 +        c.assertErrors();
   14.66 +        c.assertError("method cancel");
   14.67 +    }
   14.68 +
   14.69 +    @Test public void detectCallbackToNonExistingParams() throws IOException {
   14.70 +        String code = "package x.y.z;\n"
   14.71 +            + "import net.java.html.js.JavaScriptBody;\n"
   14.72 +            + "class X {\n"
   14.73 +            + "  @JavaScriptBody(args={\"r\"}, javacall=true, body =\n"
   14.74 +            + "    \"r.@java.lang.Runnable::run(I)(10);\"\n"
   14.75 +            + "  )\n"
   14.76 +            + "  private static native void callback(Runnable r);\n"
   14.77 +            + "}\n";
   14.78 +        
   14.79 +        Compile c = Compile.create("", code);
   14.80 +        c.assertErrors();
   14.81 +        c.assertError("wrong parameters: (I)");
   14.82 +    }
   14.83 +
   14.84 +    @Test public void objectTypeParamsAreOK() throws IOException {
   14.85 +        String code = "package x.y.z;\n"
   14.86 +            + "import net.java.html.js.JavaScriptBody;\n"
   14.87 +            + "class X {\n"
   14.88 +            + "  @JavaScriptBody(args={\"r\"}, javacall=true, body =\n"
   14.89 +            + "    \"r.@java.lang.Object::equals(Ljava/lang/Object;)(null);\"\n"
   14.90 +            + "  )\n"
   14.91 +            + "  private static native void testEqual(Object r);\n"
   14.92 +            + "}\n";
   14.93 +        
   14.94 +        Compile c = Compile.create("", code);
   14.95 +        c.assertNoErrors();
   14.96 +    }
   14.97 +    
   14.98 +    @Test public void generatesCallbacksThatReturnObject() throws Exception {
   14.99 +        Class<?> callbacksForTestPkg = Class.forName("org.apidesign.html.boot.impl.$JsCallbacks$");
  14.100 +        Method m = callbacksForTestPkg.getDeclaredMethod("java_lang_Runnable$run$", Runnable.class);
  14.101 +        assertEquals(m.getReturnType(), Object.class, "All methods always return object");
  14.102 +    }
  14.103 +    
  14.104 +    @Test public void hasInstanceField() throws Exception {
  14.105 +        Class<?> callbacksForTestPkg = Class.forName("org.apidesign.html.boot.impl.$JsCallbacks$");
  14.106 +        Field f = callbacksForTestPkg.getDeclaredField("VM");
  14.107 +        f.setAccessible(true);
  14.108 +        assertTrue(callbacksForTestPkg.isInstance(f.get(null)), "Singleton field VM");
  14.109 +    }
  14.110 +}
    15.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderBase.java	Sat Jun 29 07:01:58 2013 +0200
    15.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderBase.java	Thu Jul 18 15:39:56 2013 +0200
    15.3 @@ -136,4 +136,44 @@
    15.4              throw ex.getTargetException();
    15.5          }
    15.6      }
    15.7 +    
    15.8 +    @Test public void callJavaScriptMethodOnOwnClass() throws Throwable {
    15.9 +        try {
   15.10 +            Object thiz = methodClass.newInstance();
   15.11 +            Method st = methodClass.getMethod("returnYourSelf", methodClass);
   15.12 +            assertEquals(st.invoke(null, thiz), thiz, "Returns this");
   15.13 +        } catch (InvocationTargetException ex) {
   15.14 +            throw ex.getTargetException();
   15.15 +        }
   15.16 +    }
   15.17 +    
   15.18 +    @Test public void callStaticJavaMethod() throws Throwable {
   15.19 +        Method st = methodClass.getMethod("staticCallback", int.class, int.class);
   15.20 +        assertEquals(st.invoke(null, 6, 7), 42, "Meaning of JavaScript?");
   15.21 +    }
   15.22 +
   15.23 +    @Test public void callStaticStringParamMethod() throws Throwable {
   15.24 +        Method st = methodClass.getMethod("parseInt", String.class);
   15.25 +        assertEquals(st.invoke(null, "42"), 42, "Meaning of JavaScript?");
   15.26 +    }
   15.27 +    
   15.28 +    @Test public void firstLong() throws Throwable {
   15.29 +        Method st = methodClass.getMethod("chooseLong", boolean.class, boolean.class, long.class, long.class);
   15.30 +        assertEquals(st.invoke(null, true, false, 10, 20), 10L, "Take first value");
   15.31 +    }
   15.32 +
   15.33 +    @Test public void secondLong() throws Throwable {
   15.34 +        Method st = methodClass.getMethod("chooseLong", boolean.class, boolean.class, long.class, long.class);
   15.35 +        assertEquals(st.invoke(null, false, true, 10, 20), 20L, "Take 2nd value");
   15.36 +    }
   15.37 +
   15.38 +    @Test public void bothLong() throws Throwable {
   15.39 +        Method st = methodClass.getMethod("chooseLong", boolean.class, boolean.class, long.class, long.class);
   15.40 +        assertEquals(st.invoke(null, true, true, 10, 20), 30L, "Take both values");
   15.41 +    }
   15.42 +    
   15.43 +    @Test public void recordError() throws Throwable {
   15.44 +        Method st = methodClass.getMethod("recordError", Object.class);
   15.45 +        assertEquals(st.invoke(methodClass.newInstance(), "Hello"), "Hello", "The same parameter returned");
   15.46 +    }
   15.47  }
   15.48 \ No newline at end of file
    16.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/JsMethods.java	Sat Jun 29 07:01:58 2013 +0200
    16.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/JsMethods.java	Thu Jul 18 15:39:56 2013 +0200
    16.3 @@ -30,6 +30,8 @@
    16.4   */
    16.5  @JavaScriptResource("jsmethods.js")
    16.6  public class JsMethods {
    16.7 +    private Object value;
    16.8 +    
    16.9      @JavaScriptBody(args = {}, body = "return 42;")
   16.10      public static Object fortyTwo() {
   16.11          return -42;
   16.12 @@ -54,7 +56,7 @@
   16.13          return false;
   16.14      }
   16.15      
   16.16 -    @JavaScriptBody(args = { "r" }, javacall=true, body = "r.@java.lang.Runnable::run()()")
   16.17 +    @JavaScriptBody(args = { "r" }, javacall=true, body = "r.@java.lang.Runnable::run()();")
   16.18      public static native void callback(Runnable r);
   16.19      
   16.20      @JavaScriptBody(args = { "at", "arr" }, javacall = true, body =
   16.21 @@ -72,4 +74,34 @@
   16.22      
   16.23      @JavaScriptBody(args = { "x", "y" }, body = "return mul(x, y);")
   16.24      public static native int useExternalMul(int x, int y);
   16.25 +    
   16.26 +    @JavaScriptBody(args = { "m" }, javacall = true, body = "return m.@org.apidesign.html.boot.impl.JsMethods::getThis()();")
   16.27 +    public static native JsMethods returnYourSelf(JsMethods m);
   16.28 +    
   16.29 +    @JavaScriptBody(args = { "x", "y" }, javacall = true, body = "return @org.apidesign.html.boot.impl.JsMethods::useExternalMul(II)(x, y);")
   16.30 +    public static native int staticCallback(int x, int y);
   16.31 +
   16.32 +    @JavaScriptBody(args = { "v" }, javacall = true, body = "return @java.lang.Integer::parseInt(Ljava/lang/String;)(v);")
   16.33 +    public static native int parseInt(String v);
   16.34 +    
   16.35 +    @JavaScriptBody(args = { "useA", "useB", "a", "b" }, body = "var l = 0;"
   16.36 +        + "if (useA) l += a;\n"
   16.37 +        + "if (useB) l += b;\n"
   16.38 +        + "return l;\n"
   16.39 +    )
   16.40 +    public static native long chooseLong(boolean useA, boolean useB, long a, long b);
   16.41 +    
   16.42 +    protected void onError(Object o) throws Exception {
   16.43 +        value = o;
   16.44 +    }
   16.45 +    
   16.46 +    Object getError() {
   16.47 +        return value;
   16.48 +    }
   16.49 +    
   16.50 +    @JavaScriptBody(args = { "err" }, javacall = true, body = 
   16.51 +        "this.@org.apidesign.html.boot.impl.JsMethods::onError(Ljava/lang/Object;)(err);"
   16.52 +      + "return this.@org.apidesign.html.boot.impl.JsMethods::getError()();"
   16.53 +    )
   16.54 +    public native Object recordError(Object err);
   16.55  }
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/geo/pom.xml	Thu Jul 18 15:39:56 2013 +0200
    17.3 @@ -0,0 +1,47 @@
    17.4 +<?xml version="1.0"?>
    17.5 +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    17.6 +    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    17.7 +  <modelVersion>4.0.0</modelVersion>
    17.8 +  <parent>
    17.9 +    <groupId>org.apidesign</groupId>
   17.10 +    <artifactId>html</artifactId>
   17.11 +    <version>0.4-SNAPSHOT</version>
   17.12 +  </parent>
   17.13 +  <groupId>org.apidesign.html</groupId>
   17.14 +  <artifactId>net.java.html.geo</artifactId>
   17.15 +  <version>0.4-SNAPSHOT</version>
   17.16 +  <name>Geolocation API</name>
   17.17 +  <url>http://maven.apache.org</url>
   17.18 +  <properties>
   17.19 +    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   17.20 +  </properties>
   17.21 +  <build>
   17.22 +      <plugins>
   17.23 +          <plugin>
   17.24 +              <groupId>org.apache.maven.plugins</groupId>
   17.25 +              <artifactId>maven-javadoc-plugin</artifactId>
   17.26 +              <configuration>
   17.27 +                  <subpackages>net.java.html.geo</subpackages>
   17.28 +                  <skip>false</skip>
   17.29 +              </configuration>
   17.30 +          </plugin>
   17.31 +      </plugins>
   17.32 +  </build>
   17.33 +  <dependencies>
   17.34 +      <dependency>
   17.35 +          <groupId>org.testng</groupId>
   17.36 +          <artifactId>testng</artifactId>
   17.37 +      </dependency>
   17.38 +    <dependency>
   17.39 +      <groupId>org.netbeans.api</groupId>
   17.40 +      <artifactId>org-openide-util-lookup</artifactId>
   17.41 +      <type>jar</type>
   17.42 +    </dependency>
   17.43 +    <dependency>
   17.44 +      <groupId>org.apidesign.html</groupId>
   17.45 +      <artifactId>net.java.html.boot</artifactId>
   17.46 +      <version>0.4-SNAPSHOT</version>
   17.47 +      <type>jar</type>
   17.48 +    </dependency>
   17.49 +  </dependencies>
   17.50 +</project>
    18.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.2 +++ b/geo/src/main/java/net/java/html/geo/OnLocation.java	Thu Jul 18 15:39:56 2013 +0200
    18.3 @@ -0,0 +1,73 @@
    18.4 +/**
    18.5 + * HTML via Java(tm) Language Bindings
    18.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    18.7 + *
    18.8 + * This program is free software: you can redistribute it and/or modify
    18.9 + * it under the terms of the GNU General Public License as published by
   18.10 + * the Free Software Foundation, version 2 of the License.
   18.11 + *
   18.12 + * This program is distributed in the hope that it will be useful,
   18.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   18.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18.15 + * GNU General Public License for more details. apidesign.org
   18.16 + * designates this particular file as subject to the
   18.17 + * "Classpath" exception as provided by apidesign.org
   18.18 + * in the License file that accompanied this code.
   18.19 + *
   18.20 + * You should have received a copy of the GNU General Public License
   18.21 + * along with this program. Look for COPYING file in the top folder.
   18.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   18.23 + */
   18.24 +package net.java.html.geo;
   18.25 +
   18.26 +import java.lang.annotation.ElementType;
   18.27 +import java.lang.annotation.Retention;
   18.28 +import java.lang.annotation.RetentionPolicy;
   18.29 +import java.lang.annotation.Target;
   18.30 +
   18.31 +/** Generates a handle which can configure, {@link Position.Handle#start() start}
   18.32 + * and {@link Position.Handle#stop() stop} requests for obtaining current
   18.33 + * location of the application/device/user. Put the {@link OnLocation} annotation
   18.34 + * on top of a (non-private) method in your class which takes one argument
   18.35 + * {@link Position}. Based on name of your method (unless a class name is
   18.36 + * directly specified via {@link #className()} attribute) a handle class is
   18.37 + * generated. For example if your method name is <code>callMeWhenYouKnowWhereYouAre</code>
   18.38 + * a package private class <code>CallMeWhenYouKnowWhereYouAreHandle</code> will
   18.39 + * be generated in the same package. One can use its <code>createQuery</code>
   18.40 + * and <code>createWatch</code> static method to get one time/repeated time
   18.41 + * instance of a {@link Position.Handle handle}. After configuring the
   18.42 + * {@link Position.Handle handle} via its setter methods, one can 
   18.43 + * {@link Position.Handle#start() start} the location request.
   18.44 + * <p>
   18.45 + * In case something goes wrong a method in the same class named {@link #onError()}
   18.46 + * can be specified (should take one {@link Exception} parameter). 
   18.47 + * <p>
   18.48 + * The overall behavior of the system mimics <a href="http://www.w3.org/TR/2012/PR­geolocation­API­20120510/">
   18.49 + * W3C's Geolocation API</a>.
   18.50 + *
   18.51 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   18.52 + */
   18.53 +@Retention(RetentionPolicy.SOURCE)
   18.54 +@Target(ElementType.METHOD)
   18.55 +public @interface OnLocation {
   18.56 +    /** Name of the {@link Position.Handle handle} class to generate. By
   18.57 +     * default the name is derived from the name of annotated method by
   18.58 +     * capitalizing the first character and appending <code>Handle</code>.
   18.59 +     * <p>
   18.60 +     * The generated class contains two static methods: <code>createQuery</code>
   18.61 +     * and <code>createWatch</code> which take no parameters if this method 
   18.62 +     * is static or one parameter (of this class) if this method is instance
   18.63 +     * one. Both static methods return {@link Position.Handle}.
   18.64 +     * 
   18.65 +     * @return string suitable for a new class in the same package
   18.66 +     */
   18.67 +    public String className() default "";
   18.68 +    
   18.69 +    /** Name of a method in this class which should be called in case of 
   18.70 +     * an error. The method has to be non-private and take {@link Exception} 
   18.71 +     * parameter. By default the exception printed to console.
   18.72 +     * 
   18.73 +     * @return name of method in this class
   18.74 +     */
   18.75 +    public String onError() default "";
   18.76 +}
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/geo/src/main/java/net/java/html/geo/Position.java	Thu Jul 18 15:39:56 2013 +0200
    19.3 @@ -0,0 +1,248 @@
    19.4 +/**
    19.5 + * HTML via Java(tm) Language Bindings
    19.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    19.7 + *
    19.8 + * This program is free software: you can redistribute it and/or modify
    19.9 + * it under the terms of the GNU General Public License as published by
   19.10 + * the Free Software Foundation, version 2 of the License.
   19.11 + *
   19.12 + * This program is distributed in the hope that it will be useful,
   19.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   19.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19.15 + * GNU General Public License for more details. apidesign.org
   19.16 + * designates this particular file as subject to the
   19.17 + * "Classpath" exception as provided by apidesign.org
   19.18 + * in the License file that accompanied this code.
   19.19 + *
   19.20 + * You should have received a copy of the GNU General Public License
   19.21 + * along with this program. Look for COPYING file in the top folder.
   19.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   19.23 + */
   19.24 +package net.java.html.geo;
   19.25 +
   19.26 +import java.util.logging.Level;
   19.27 +import java.util.logging.Logger;
   19.28 +import org.apidesign.html.geo.impl.JsG;
   19.29 +
   19.30 +/** Class that represents a geolocation position provided as a callback
   19.31 + * to {@link Handle#onLocation(net.java.html.geo.Position)} method. The
   19.32 + * class getters mimic closely the structure of the position object as
   19.33 + * specified by <a href="http://www.w3.org/TR/2012/PR­geolocation­API­20120510/">
   19.34 + * W3C's Geolocation API</a>.
   19.35 + *
   19.36 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   19.37 + */
   19.38 +public final class Position {
   19.39 +    static final Logger LOG = Logger.getLogger(Position.class.getName());
   19.40 +    private final long timestamp;
   19.41 +    private final Coordinates coords;
   19.42 +
   19.43 +    Position(Object position) {
   19.44 +        Object obj = JsG.get(position, "timestamp");
   19.45 +        timestamp = obj instanceof Number ? ((Number)obj).longValue() : 0L;
   19.46 +        coords = new Coordinates(JsG.get(position, "coords"));
   19.47 +    }
   19.48 +    
   19.49 +    /** The actual location of the position.
   19.50 +     * @return non-null coordinates
   19.51 +     */
   19.52 +    public Coordinates getCoords() {
   19.53 +        return coords;
   19.54 +    }
   19.55 +    
   19.56 +    /** The time when the position has been recorded.
   19.57 +     * @return time in milliseconds since era (e.g. Jan 1, 1970).
   19.58 +     */
   19.59 +    public long getTimestamp() {
   19.60 +        return timestamp;
   19.61 +    }
   19.62 +
   19.63 +    /** Actual location of a {@link Position}. 
   19.64 +     *  Mimics closely <a href="http://www.w3.org/TR/2012/PR­geolocation­API­20120510/">
   19.65 +     * W3C's Geolocation API</a>.
   19.66 +     */
   19.67 +    public static final class Coordinates {
   19.68 +        private final Object data;
   19.69 +
   19.70 +        Coordinates(Object data) {
   19.71 +            this.data = data;
   19.72 +        }
   19.73 +        
   19.74 +        public double getLatitude() {
   19.75 +            return ((Number)JsG.get(data, "latitude")).doubleValue(); // NOI18N
   19.76 +        }
   19.77 +
   19.78 +        public double getLongitude() {
   19.79 +            return ((Number)JsG.get(data, "longitude")).doubleValue(); // NOI18N
   19.80 +        }
   19.81 +
   19.82 +        public double getAccuracy() {
   19.83 +            return ((Number)JsG.get(data, "accuracy")).doubleValue(); // NOI18N
   19.84 +        }
   19.85 +        
   19.86 +        public Double getAltitude() {
   19.87 +            return (Double)JsG.get(data, "altitude"); // NOI18N
   19.88 +        }
   19.89 +        
   19.90 +        public Double getAltitudeAccuracy() {
   19.91 +            return (Double)JsG.get(data, "altitudeAccuracy"); // NOI18N
   19.92 +        }
   19.93 +        
   19.94 +        public Double getHeading() {
   19.95 +            return (Double)JsG.get(data, "heading"); // NOI18N
   19.96 +        }
   19.97 +        
   19.98 +        public Double getSpeed() {
   19.99 +            return (Double)JsG.get(data, "speed"); // NOI18N
  19.100 +        }
  19.101 +    } // end of Coordinates
  19.102 +
  19.103 +    /** Rather than subclassing this class directly consider using {@link OnLocation}
  19.104 +     * annotation. Such annotation will generate a subclass for you automatically
  19.105 +     * with two static methods <code>createQuery</code> and <code>createWatch</code>
  19.106 +     * which can be used to obtain instance of this class.
  19.107 +     */
  19.108 +    public static abstract class Handle {
  19.109 +        private final boolean oneTime;
  19.110 +        private boolean enableHighAccuracy;
  19.111 +        private long timeout;
  19.112 +        private long maximumAge;
  19.113 +        volatile JsH handle;
  19.114 +
  19.115 +        /** Creates new instance of this handle.
  19.116 +         * 
  19.117 +         * @param oneTime <code>true</code> if the handle represents one time 
  19.118 +         *   <em>query</em>. <code>false</code> if it represents a <em>watch</em>
  19.119 +         */
  19.120 +        protected Handle(boolean oneTime) {
  19.121 +            super();
  19.122 +            this.oneTime = oneTime;
  19.123 +        }
  19.124 +
  19.125 +        /** Callback from the implementation when a (new) position has been
  19.126 +         * received and identified
  19.127 +         * @param p the position
  19.128 +         * @throws Throwable if an exception is thrown, it will be logged by the system
  19.129 +         */
  19.130 +        protected abstract void onLocation(Position p) throws Throwable;
  19.131 +
  19.132 +        /** Callback when an error occurs.
  19.133 +         * @param ex the exception describing what went wrong
  19.134 +         * @throws Throwable if an exception is thrown, it will be logged by the system
  19.135 +         */
  19.136 +        protected abstract void onError(Exception ex) throws Throwable;
  19.137 +        
  19.138 +        /** Check whether the location API is supported.
  19.139 +         * @return true, if one can call {@link #start}.
  19.140 +         */
  19.141 +        public final boolean isSupported() {
  19.142 +            return JsG.hasGeolocation();
  19.143 +        }
  19.144 +
  19.145 +        /** Turns on high accuracy mode as specified by the 
  19.146 +         * <a href="http://www.w3.org/TR/2012/PR­geolocation­API­20120510/">
  19.147 +         * W3C's Geolocation API</a>. By default the mode is disabled.
  19.148 +         * @param enable <code>true</code> or <code>false</code>
  19.149 +         */
  19.150 +        public final void setHighAccuracy(boolean enable) {
  19.151 +            this.enableHighAccuracy = enable;
  19.152 +        }
  19.153 +
  19.154 +        /** The amount of milliseconds to wait for a result.
  19.155 +         * By default infinity.
  19.156 +         * @param timeout time in milliseconds to wait for a result.
  19.157 +         */
  19.158 +        public final void setTimeout(long timeout) {
  19.159 +            this.timeout = timeout;
  19.160 +        }
  19.161 +
  19.162 +        /** Sets maximum age of cached results which are acceptable to be
  19.163 +         * returned. By default maximum age is set to zero.
  19.164 +         * @param age time in milliseconds of acceptable cached results
  19.165 +         */
  19.166 +        public final void setMaximumAge(long age) {
  19.167 +            this.maximumAge = age;
  19.168 +        }
  19.169 +        
  19.170 +        /** Initializes the <em>query</em> or <em>watch</em> request(s) and
  19.171 +         * returns immediately. Has no effect if the query has already been
  19.172 +         * started. If a problem appears while starting the system,
  19.173 +         * it is immediately reported via the {@link #onError(java.lang.Exception)}
  19.174 +         * callback. For example, if the {@link #isSupported()} method
  19.175 +         * returns <code>false</code> an IllegalStateException is created
  19.176 +         * and sent to the {@link #onError(java.lang.Exception) callback} method.
  19.177 +         */
  19.178 +        public final void start() {
  19.179 +            if (handle != null) {
  19.180 +                return;
  19.181 +            }
  19.182 +            handle = new JsH();
  19.183 +            try {
  19.184 +                if (!isSupported()) {
  19.185 +                    throw new IllegalStateException("geolocation API not supported");
  19.186 +                }
  19.187 +                handle.start();
  19.188 +            } catch (Exception ex) {
  19.189 +                try {
  19.190 +                    onError(ex);
  19.191 +                } catch (Throwable thr) {
  19.192 +                    LOG.log(Level.INFO, "Problems delivering onError report", thr);
  19.193 +                }
  19.194 +            }
  19.195 +        }
  19.196 +
  19.197 +        /** Stops all pending requests. After this call no further callbacks
  19.198 +         * can be obtained. Does nothing if no query or watch was in progress.
  19.199 +         */
  19.200 +        public final void stop() {
  19.201 +            JsH h = handle;
  19.202 +            if (h == null) {
  19.203 +                return;
  19.204 +            }
  19.205 +            handle = null;
  19.206 +            h.stop();
  19.207 +        }
  19.208 +
  19.209 +        private final class JsH extends JsG {
  19.210 +            long watch;
  19.211 +            
  19.212 +            @Override
  19.213 +            public void onLocation(Object position) {
  19.214 +                if (handle != this) {
  19.215 +                    return;
  19.216 +                }
  19.217 +                if (oneTime) {
  19.218 +                    stop();
  19.219 +                }
  19.220 +                try {
  19.221 +                    Handle.this.onLocation(new Position(position));
  19.222 +                } catch (Throwable ex) {
  19.223 +                    LOG.log(Level.SEVERE, null, ex);
  19.224 +                }
  19.225 +            }
  19.226 +
  19.227 +            @Override
  19.228 +            public void onError(Object error) {
  19.229 +                if (handle != this) {
  19.230 +                    return;
  19.231 +                }
  19.232 +                if (oneTime) {
  19.233 +                    stop();
  19.234 +                }
  19.235 +                try {
  19.236 +                    Handle.this.onError(new Exception());
  19.237 +                } catch (Throwable ex) {
  19.238 +                    LOG.log(Level.SEVERE, null, ex);
  19.239 +                }
  19.240 +            }
  19.241 +
  19.242 +            final void start() {
  19.243 +                watch = start(oneTime, enableHighAccuracy, timeout, maximumAge);
  19.244 +            }
  19.245 +
  19.246 +            protected final void stop() {
  19.247 +                super.stop(watch);
  19.248 +            }
  19.249 +        }
  19.250 +    }
  19.251 +}
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/geo/src/main/java/net/java/html/geo/package.html	Thu Jul 18 15:39:56 2013 +0200
    20.3 @@ -0,0 +1,37 @@
    20.4 +<!--
    20.5 +
    20.6 +    HTML via Java(tm) Language Bindings
    20.7 +    Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    20.8 +
    20.9 +    This program is free software: you can redistribute it and/or modify
   20.10 +    it under the terms of the GNU General Public License as published by
   20.11 +    the Free Software Foundation, version 2 of the License.
   20.12 +
   20.13 +    This program is distributed in the hope that it will be useful,
   20.14 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
   20.15 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   20.16 +    GNU General Public License for more details. apidesign.org
   20.17 +    designates this particular file as subject to the
   20.18 +    "Classpath" exception as provided by apidesign.org
   20.19 +    in the License file that accompanied this code.
   20.20 +
   20.21 +    You should have received a copy of the GNU General Public License
   20.22 +    along with this program. Look for COPYING file in the top folder.
   20.23 +    If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   20.24 +
   20.25 +-->
   20.26 +<!DOCTYPE html>
   20.27 +<html>
   20.28 +    <head>
   20.29 +        <title>Geolocation API for Java</title>
   20.30 +        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
   20.31 +        <meta name="viewport" content="width=device-width">
   20.32 +    </head>
   20.33 +    <body>
   20.34 +        <div>
   20.35 +        HTML Geo API for Java provides <a href="OnLocation.html">annotation based way</a> of
   20.36 +        obtaining geolocation information from a browser or any other 
   20.37 +        device capable of providing it.
   20.38 +        </div>
   20.39 +    </body>
   20.40 +</html>
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/geo/src/main/java/org/apidesign/html/geo/impl/GeoProcessor.java	Thu Jul 18 15:39:56 2013 +0200
    21.3 @@ -0,0 +1,199 @@
    21.4 +/**
    21.5 + * HTML via Java(tm) Language Bindings
    21.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    21.7 + *
    21.8 + * This program is free software: you can redistribute it and/or modify
    21.9 + * it under the terms of the GNU General Public License as published by
   21.10 + * the Free Software Foundation, version 2 of the License.
   21.11 + *
   21.12 + * This program is distributed in the hope that it will be useful,
   21.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   21.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   21.15 + * GNU General Public License for more details. apidesign.org
   21.16 + * designates this particular file as subject to the
   21.17 + * "Classpath" exception as provided by apidesign.org
   21.18 + * in the License file that accompanied this code.
   21.19 + *
   21.20 + * You should have received a copy of the GNU General Public License
   21.21 + * along with this program. Look for COPYING file in the top folder.
   21.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   21.23 + */
   21.24 +package org.apidesign.html.geo.impl;
   21.25 +
   21.26 +import java.io.IOException;
   21.27 +import java.io.Writer;
   21.28 +import java.util.Locale;
   21.29 +import java.util.Set;
   21.30 +import java.util.logging.Level;
   21.31 +import java.util.logging.Logger;
   21.32 +import javax.annotation.processing.AbstractProcessor;
   21.33 +import javax.annotation.processing.Processor;
   21.34 +import javax.annotation.processing.RoundEnvironment;
   21.35 +import javax.annotation.processing.SupportedAnnotationTypes;
   21.36 +import javax.annotation.processing.SupportedSourceVersion;
   21.37 +import javax.lang.model.SourceVersion;
   21.38 +import javax.lang.model.element.Element;
   21.39 +import javax.lang.model.element.ElementKind;
   21.40 +import javax.lang.model.element.ExecutableElement;
   21.41 +import javax.lang.model.element.Modifier;
   21.42 +import javax.lang.model.element.PackageElement;
   21.43 +import javax.lang.model.element.TypeElement;
   21.44 +import javax.lang.model.type.TypeMirror;
   21.45 +import javax.tools.Diagnostic;
   21.46 +import javax.tools.JavaFileObject;
   21.47 +import net.java.html.geo.OnLocation;
   21.48 +import net.java.html.geo.Position;
   21.49 +import org.openide.util.lookup.ServiceProvider;
   21.50 +
   21.51 +/** Annotation processor to generate callbacks from {@link GeoHandle} class.
   21.52 + *
   21.53 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   21.54 + */
   21.55 +@ServiceProvider(service=Processor.class)
   21.56 +@SupportedSourceVersion(SourceVersion.RELEASE_6)
   21.57 +@SupportedAnnotationTypes({
   21.58 +    "net.java.html.geo.OnLocation"
   21.59 +})
   21.60 +public final class GeoProcessor extends AbstractProcessor {
   21.61 +    private static final Logger LOG = Logger.getLogger(GeoProcessor.class.getName());
   21.62 +    @Override
   21.63 +    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
   21.64 +        boolean ok = true;
   21.65 +        for (Element e : roundEnv.getElementsAnnotatedWith(OnLocation.class)) {
   21.66 +            if (!processLocation(e)) {
   21.67 +                ok = false;
   21.68 +            }
   21.69 +        }
   21.70 +        return ok;
   21.71 +    }
   21.72 +
   21.73 +    private void error(String msg, Element e) {
   21.74 +        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
   21.75 +    }
   21.76 +    
   21.77 +    private boolean processLocation(Element e) {
   21.78 +        if (e.getKind() != ElementKind.METHOD) {
   21.79 +            return false;
   21.80 +        }
   21.81 +        ExecutableElement me = (ExecutableElement) e;
   21.82 +        OnLocation ol = e.getAnnotation(OnLocation.class);
   21.83 +        if (ol == null) {
   21.84 +            return true;
   21.85 +        }
   21.86 +        if (me.getModifiers().contains(Modifier.PRIVATE)) {
   21.87 +            error("Method annotated by @OnLocation cannot be private", e);
   21.88 +            return false;
   21.89 +        }
   21.90 +        TypeMirror positionClass = processingEnv.getElementUtils().getTypeElement(Position.class.getName()).asType();
   21.91 +        if (me.getParameters().size() != 1 || !me.getParameters().get(0).asType().equals(positionClass)) {
   21.92 +            error("Method annotated by @OnLocation needs to have one net.java.html.geo.Position argument!", e);
   21.93 +            return false;
   21.94 +        }
   21.95 +        String className = ol.className();
   21.96 +        if (className.isEmpty()) {
   21.97 +            String n = e.getSimpleName().toString();
   21.98 +            if (n.isEmpty()) {
   21.99 +                error("Empty method name", e);
  21.100 +                return false;
  21.101 +            }
  21.102 +            final String firstLetter = n.substring(0, 1).toUpperCase(Locale.ENGLISH);
  21.103 +            className = firstLetter + n.substring(1) + "Handle";
  21.104 +        }
  21.105 +        TypeElement te = (TypeElement)e.getEnclosingElement();
  21.106 +        PackageElement pe = (PackageElement) te.getEnclosingElement();
  21.107 +        final String pkg = pe.getQualifiedName().toString();
  21.108 +        final String fqn = pkg + "." + className;
  21.109 +        final boolean isStatic = me.getModifiers().contains(Modifier.STATIC);
  21.110 +        try {
  21.111 +            JavaFileObject fo = processingEnv.getFiler().createSourceFile(fqn, e);
  21.112 +            Writer w = fo.openWriter();
  21.113 +            w.append("package ").append(pkg).append(";\n");
  21.114 +            w.append("class ").append(className).append(" extends net.java.html.geo.Position.Handle {\n");
  21.115 +            w.append("  private ").append(te.getSimpleName()).append(" i;\n");
  21.116 +            w.append("  private ").append(className).append("(boolean oneTime");
  21.117 +            w.append(", ").append(te.getSimpleName()).append(" i");
  21.118 +            w.append(") {\n    super(oneTime);\n");
  21.119 +            if (!isStatic) {
  21.120 +                w.append("    this.i = i;\n");
  21.121 +            }
  21.122 +            w.append("}\n");
  21.123 +            w.append("  static net.java.html.geo.Position.Handle createQuery(");
  21.124 +            String inst;
  21.125 +            if (!isStatic) {
  21.126 +                w.append(te.getSimpleName()).append(" instance");
  21.127 +                inst = "instance";
  21.128 +            } else {
  21.129 +                inst = "null";
  21.130 +            }
  21.131 +            w.append(") { return new ").append(className).append("(true, ").append(inst).append("); }\n");
  21.132 +            w.append("  static net.java.html.geo.Position.Handle createWatch(");
  21.133 +            if (!isStatic) {
  21.134 +                w.append(te.getSimpleName()).append(" instance");
  21.135 +            }
  21.136 +            w.append(") { return new ").append(className).append("(false, ").append(inst).append("); }\n");
  21.137 +            w.append("  @Override protected void onError(Exception t) throws Throwable {\n");
  21.138 +            if (ol.onError().isEmpty()) {
  21.139 +                w.append("    t.printStackTrace();");
  21.140 +            } else {
  21.141 +                if (!findOnError(me, te, ol.onError(), isStatic)) {
  21.142 +                    return false;
  21.143 +                }
  21.144 +                if (isStatic) {
  21.145 +                    w.append("    ").append(te.getSimpleName()).append(".");
  21.146 +                } else {
  21.147 +                    w.append("    i.");
  21.148 +                }
  21.149 +                w.append(ol.onError()).append("(t);\n");
  21.150 +            }
  21.151 +            w.append("  }\n");
  21.152 +            w.append("  @Override protected void onLocation(net.java.html.geo.Position p) throws Throwable {\n");
  21.153 +            if (isStatic) {
  21.154 +                w.append("    ").append(te.getSimpleName()).append(".");
  21.155 +            } else {
  21.156 +                w.append("    i.");
  21.157 +            }
  21.158 +            w.append(me.getSimpleName()).append("(p);\n");
  21.159 +            w.append("  }\n");
  21.160 +            w.append("}\n");
  21.161 +            w.close();
  21.162 +        } catch (IOException ex) {
  21.163 +            Logger.getLogger(GeoProcessor.class.getName()).log(Level.SEVERE, null, ex);
  21.164 +            error("Can't write handler class: " + ex.getMessage(), e);
  21.165 +            return false;
  21.166 +        }
  21.167 +        
  21.168 +        return true;
  21.169 +    }
  21.170 +
  21.171 +    private boolean findOnError(Element errElem, TypeElement te, String name, boolean onlyStatic) {
  21.172 +        String err = null;
  21.173 +        for (Element e : te.getEnclosedElements()) {
  21.174 +            if (e.getKind() != ElementKind.METHOD) {
  21.175 +                continue;
  21.176 +            }
  21.177 +            if (!e.getSimpleName().contentEquals(name)) {
  21.178 +                continue;
  21.179 +            }
  21.180 +            if (onlyStatic && !e.getModifiers().contains(Modifier.STATIC)) {
  21.181 +                errElem = e;
  21.182 +                err = "Would have to be static";
  21.183 +                continue;
  21.184 +            }
  21.185 +            ExecutableElement ee = (ExecutableElement) e;
  21.186 +            TypeMirror excType = processingEnv.getElementUtils().getTypeElement(Exception.class.getName()).asType();
  21.187 +            if (ee.getParameters().size() != 1 || 
  21.188 +                !processingEnv.getTypeUtils().isAssignable(excType, ee.getParameters().get(0).asType())
  21.189 +            ) {
  21.190 +                errElem = e;
  21.191 +                err = "Error method needs to take one Exception argument";
  21.192 +                continue;
  21.193 +            }
  21.194 +            return true;
  21.195 +        }
  21.196 +        if (err == null) {
  21.197 +            err = "Cannot find " + name + "(Exception) method in this class";
  21.198 +        }
  21.199 +        error(err, errElem);
  21.200 +        return false;
  21.201 +    }
  21.202 +}
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/geo/src/main/java/org/apidesign/html/geo/impl/JsG.java	Thu Jul 18 15:39:56 2013 +0200
    22.3 @@ -0,0 +1,85 @@
    22.4 +/**
    22.5 + * HTML via Java(tm) Language Bindings
    22.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    22.7 + *
    22.8 + * This program is free software: you can redistribute it and/or modify
    22.9 + * it under the terms of the GNU General Public License as published by
   22.10 + * the Free Software Foundation, version 2 of the License.
   22.11 + *
   22.12 + * This program is distributed in the hope that it will be useful,
   22.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   22.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   22.15 + * GNU General Public License for more details. apidesign.org
   22.16 + * designates this particular file as subject to the
   22.17 + * "Classpath" exception as provided by apidesign.org
   22.18 + * in the License file that accompanied this code.
   22.19 + *
   22.20 + * You should have received a copy of the GNU General Public License
   22.21 + * along with this program. Look for COPYING file in the top folder.
   22.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   22.23 + */
   22.24 +package org.apidesign.html.geo.impl;
   22.25 +
   22.26 +import net.java.html.js.JavaScriptBody;
   22.27 +
   22.28 +/** Implementation class to deal with browser's <code>navigator.geolocation</code> 
   22.29 + * object.
   22.30 + *
   22.31 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   22.32 + */
   22.33 +public abstract class JsG {
   22.34 +    protected JsG() {
   22.35 +        if (!getClass().getName().equals("net.java.html.geo.Position$Handle$JsH")) {
   22.36 +            throw new IllegalStateException();
   22.37 +        }
   22.38 +    }
   22.39 +    
   22.40 +    public abstract void onLocation(Object position);
   22.41 +    public abstract void onError(Object error);
   22.42 +    
   22.43 +    @JavaScriptBody(args = {}, body = "return !!navigator.geolocation;")
   22.44 +    public static boolean hasGeolocation() {
   22.45 +        return false;
   22.46 +    }
   22.47 +
   22.48 +    @JavaScriptBody(
   22.49 +        args = { "onlyOnce", "enableHighAccuracy", "timeout", "maximumAge" }, 
   22.50 +        javacall = true, 
   22.51 +        body = 
   22.52 +        "var self = this;\n" +
   22.53 +        "var ok = function (position) {\n" +
   22.54 +        "  self.@org.apidesign.html.geo.impl.JsG::onLocation(Ljava/lang/Object;)(position);\n" +
   22.55 +        "};\n" +
   22.56 +        "var fail = function (error) {\n" +
   22.57 +        "  self.@org.apidesign.html.geo.impl.JsG::onError(Ljava/lang/Object;)(error);\n" +
   22.58 +        "};\n" +
   22.59 +        "var options = {};\n" +
   22.60 +        "options.enableHighAccuracy = enableHighAccuracy;\n" +
   22.61 +        "if (timeout >= 0) options.timeout = timeout;\n" +
   22.62 +        "if (maximumAge >= 0) options.maximumAge = maximumAge;\n" +
   22.63 +        "if (onlyOnce) {\n" +
   22.64 +        "  navigator.geolocation.getCurrentPosition(ok, fail);\n" +
   22.65 +        "  return 0;\n" +
   22.66 +        "} else {\n" +
   22.67 +        "  return navigator.geolocation.watchPosition(ok, fail);\n" +
   22.68 +        "}\n"
   22.69 +    )
   22.70 +    protected long start(
   22.71 +        boolean onlyOnce, 
   22.72 +        boolean enableHighAccuracy,
   22.73 +        long timeout,
   22.74 +        long maximumAge
   22.75 +    ) {
   22.76 +        return -1;
   22.77 +    }
   22.78 +    
   22.79 +    @JavaScriptBody(args = { "watch" }, body = "navigator.geolocation.clearWatch(watch);")
   22.80 +    protected void stop(long watch) {
   22.81 +    }
   22.82 +
   22.83 +    @JavaScriptBody(args = { "self", "property" }, body = "return self[property];")
   22.84 +    public static Object get(Object self, String property) {
   22.85 +        return null;
   22.86 +    }
   22.87 +
   22.88 +}
    23.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.2 +++ b/geo/src/test/java/net/java/html/geo/OnLocationTest.java	Thu Jul 18 15:39:56 2013 +0200
    23.3 @@ -0,0 +1,107 @@
    23.4 +/**
    23.5 + * HTML via Java(tm) Language Bindings
    23.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    23.7 + *
    23.8 + * This program is free software: you can redistribute it and/or modify
    23.9 + * it under the terms of the GNU General Public License as published by
   23.10 + * the Free Software Foundation, version 2 of the License.
   23.11 + *
   23.12 + * This program is distributed in the hope that it will be useful,
   23.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   23.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   23.15 + * GNU General Public License for more details. apidesign.org
   23.16 + * designates this particular file as subject to the
   23.17 + * "Classpath" exception as provided by apidesign.org
   23.18 + * in the License file that accompanied this code.
   23.19 + *
   23.20 + * You should have received a copy of the GNU General Public License
   23.21 + * along with this program. Look for COPYING file in the top folder.
   23.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   23.23 + */
   23.24 +package net.java.html.geo;
   23.25 +
   23.26 +import org.testng.annotations.Test;
   23.27 +import static org.testng.Assert.*;
   23.28 +
   23.29 +/** Testing correctness of the generated code.
   23.30 + */
   23.31 +public class OnLocationTest {
   23.32 +    static int cnt;
   23.33 +    static @OnLocation void onLocation(Position p) {
   23.34 +        assertNotNull(p, "Position object provided");
   23.35 +        cnt++;
   23.36 +    }
   23.37 +
   23.38 +    @Test public void createOneTimeQueryStatic() {
   23.39 +        net.java.html.geo.Position.Handle h = OnLocationHandle.createQuery();
   23.40 +        h.setHighAccuracy(false);
   23.41 +        h.setTimeout(1000L);
   23.42 +        h.setMaximumAge(1000L);
   23.43 +        if (h.isSupported()) h.start();
   23.44 +        h.stop();
   23.45 +    }
   23.46 +    
   23.47 +    @Test public void onLocationHandleCallback() throws Throwable {
   23.48 +        net.java.html.geo.Position.Handle h = OnLocationHandle.createQuery();
   23.49 +        cnt = 0;
   23.50 +        h.onLocation(new Position(null));
   23.51 +        assertEquals(cnt, 1, "The callback has been made");
   23.52 +    }
   23.53 +
   23.54 +    @Test public void createRepeatableWatchStatic() {
   23.55 +        net.java.html.geo.Position.Handle h = OnLocationHandle.createQuery();
   23.56 +        h.setHighAccuracy(false);
   23.57 +        h.setTimeout(1000L);
   23.58 +        h.setMaximumAge(1000L);
   23.59 +        if (h.isSupported()) h.start();
   23.60 +        h.stop();
   23.61 +    }
   23.62 +
   23.63 +    int instCnt;
   23.64 +    Throwable instT;
   23.65 +    @OnLocation(onError = "someError") void instance(Position p) throws Error {
   23.66 +        assertNotNull(p, "Some position passed in");
   23.67 +        instCnt++;
   23.68 +    }
   23.69 +    void someError(Throwable t) throws Exception {
   23.70 +        instT = t;
   23.71 +        instCnt++;
   23.72 +    }
   23.73 +    
   23.74 +    @Test public void createOneTimeQueryInstance() {
   23.75 +        OnLocationTest t = new OnLocationTest();
   23.76 +        
   23.77 +        net.java.html.geo.Position.Handle h = InstanceHandle.createQuery(t);
   23.78 +        h.setHighAccuracy(false);
   23.79 +        h.setTimeout(1000L);
   23.80 +        h.setMaximumAge(1000L);
   23.81 +        if (h.isSupported()) h.start();
   23.82 +        h.stop();
   23.83 +    }
   23.84 +    
   23.85 +    @Test public void onInstanceCallback() throws Throwable {
   23.86 +        OnLocationTest t = new OnLocationTest();
   23.87 +        net.java.html.geo.Position.Handle h = InstanceHandle.createWatch(t);
   23.88 +        h.onLocation(new Position(null));
   23.89 +        assertEquals(t.instCnt, 1, "One callback made");
   23.90 +    }
   23.91 +
   23.92 +    @Test public void onInstanceError() throws Throwable {
   23.93 +        net.java.html.geo.Position.Handle h = InstanceHandle.createWatch(this);
   23.94 +        InterruptedException e = new InterruptedException();
   23.95 +        h.onError(e);
   23.96 +        assertEquals(instCnt, 1, "One callback made");
   23.97 +        assertEquals(instT, e, "The same exception passed in");
   23.98 +    }
   23.99 +
  23.100 +    @Test public void createRepeatableWatch() {
  23.101 +        OnLocationTest t = new OnLocationTest();
  23.102 +        
  23.103 +        net.java.html.geo.Position.Handle h = InstanceHandle.createWatch(t);
  23.104 +        h.setHighAccuracy(false);
  23.105 +        h.setTimeout(1000L);
  23.106 +        h.setMaximumAge(1000L);
  23.107 +        if (h.isSupported()) h.start();
  23.108 +        h.stop();
  23.109 +    }
  23.110 +}
    24.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.2 +++ b/geo/src/test/java/org/apidesign/html/geo/impl/Compile.java	Thu Jul 18 15:39:56 2013 +0200
    24.3 @@ -0,0 +1,264 @@
    24.4 +/**
    24.5 + * HTML via Java(tm) Language Bindings
    24.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    24.7 + *
    24.8 + * This program is free software: you can redistribute it and/or modify
    24.9 + * it under the terms of the GNU General Public License as published by
   24.10 + * the Free Software Foundation, version 2 of the License.
   24.11 + *
   24.12 + * This program is distributed in the hope that it will be useful,
   24.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   24.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   24.15 + * GNU General Public License for more details. apidesign.org
   24.16 + * designates this particular file as subject to the
   24.17 + * "Classpath" exception as provided by apidesign.org
   24.18 + * in the License file that accompanied this code.
   24.19 + *
   24.20 + * You should have received a copy of the GNU General Public License
   24.21 + * along with this program. Look for COPYING file in the top folder.
   24.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   24.23 + */
   24.24 +package org.apidesign.html.geo.impl;
   24.25 +
   24.26 +import java.io.ByteArrayInputStream;
   24.27 +import java.io.ByteArrayOutputStream;
   24.28 +import java.io.IOException;
   24.29 +import java.io.InputStream;
   24.30 +import java.io.OutputStream;
   24.31 +import java.net.URI;
   24.32 +import java.net.URISyntaxException;
   24.33 +import java.util.ArrayList;
   24.34 +import java.util.Arrays;
   24.35 +import java.util.HashMap;
   24.36 +import java.util.List;
   24.37 +import java.util.Locale;
   24.38 +import java.util.Map;
   24.39 +import java.util.regex.Matcher;
   24.40 +import java.util.regex.Pattern;
   24.41 +import javax.tools.Diagnostic;
   24.42 +import javax.tools.DiagnosticListener;
   24.43 +import javax.tools.FileObject;
   24.44 +import javax.tools.ForwardingJavaFileManager;
   24.45 +import javax.tools.JavaFileManager;
   24.46 +import javax.tools.JavaFileObject;
   24.47 +import javax.tools.JavaFileObject.Kind;
   24.48 +import javax.tools.SimpleJavaFileObject;
   24.49 +import javax.tools.StandardJavaFileManager;
   24.50 +import javax.tools.StandardLocation;
   24.51 +import javax.tools.ToolProvider;
   24.52 +import static org.testng.Assert.*;
   24.53 +
   24.54 +/**
   24.55 + *
   24.56 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   24.57 + */
   24.58 +final class Compile implements DiagnosticListener<JavaFileObject> {
   24.59 +    private final List<Diagnostic<? extends JavaFileObject>> errors = 
   24.60 +            new ArrayList<Diagnostic<? extends JavaFileObject>>();
   24.61 +    private final Map<String, byte[]> classes;
   24.62 +    private final String pkg;
   24.63 +    private final String cls;
   24.64 +    private final String html;
   24.65 +    private final String sourceLevel;
   24.66 +
   24.67 +    private Compile(String html, String code, String sl) throws IOException {
   24.68 +        this.pkg = findPkg(code);
   24.69 +        this.cls = findCls(code);
   24.70 +        this.html = html;
   24.71 +        this.sourceLevel = sl;
   24.72 +        classes = compile(html, code);
   24.73 +    }
   24.74 +
   24.75 +    /** Performs compilation of given HTML page and associated Java code
   24.76 +     */
   24.77 +    public static Compile create(String html, String code) throws IOException {
   24.78 +        return create(html, code, "1.7");
   24.79 +    }
   24.80 +    static Compile create(String html, String code, String sourceLevel) throws IOException {
   24.81 +        return new Compile(html, code, sourceLevel);
   24.82 +    }
   24.83 +    
   24.84 +    /** Checks for given class among compiled resources */
   24.85 +    public byte[] get(String res) {
   24.86 +        return classes.get(res);
   24.87 +    }
   24.88 +    
   24.89 +    /** Obtains errors created during compilation.
   24.90 +     */
   24.91 +    public List<Diagnostic<? extends JavaFileObject>> getErrors() {
   24.92 +        List<Diagnostic<? extends JavaFileObject>> err;
   24.93 +        err = new ArrayList<Diagnostic<? extends JavaFileObject>>();
   24.94 +        for (Diagnostic<? extends JavaFileObject> diagnostic : errors) {
   24.95 +            if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
   24.96 +                err.add(diagnostic);
   24.97 +            }
   24.98 +        }
   24.99 +        return err;
  24.100 +    }
  24.101 +    
  24.102 +    private Map<String, byte[]> compile(final String html, final String code) throws IOException {
  24.103 +        StandardJavaFileManager sjfm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(this, null, null);
  24.104 +
  24.105 +        final Map<String, ByteArrayOutputStream> class2BAOS;
  24.106 +        class2BAOS = new HashMap<String, ByteArrayOutputStream>();
  24.107 +
  24.108 +        JavaFileObject file = new SimpleJavaFileObject(URI.create("mem://mem"), Kind.SOURCE) {
  24.109 +            @Override
  24.110 +            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
  24.111 +                return code;
  24.112 +            }
  24.113 +        };
  24.114 +        final JavaFileObject htmlFile = new SimpleJavaFileObject(URI.create("mem://mem2"), Kind.OTHER) {
  24.115 +            @Override
  24.116 +            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
  24.117 +                return html;
  24.118 +            }
  24.119 +
  24.120 +            @Override
  24.121 +            public InputStream openInputStream() throws IOException {
  24.122 +                return new ByteArrayInputStream(html.getBytes());
  24.123 +            }
  24.124 +        };
  24.125 +        
  24.126 +        final URI scratch;
  24.127 +        try {
  24.128 +            scratch = new URI("mem://mem3");
  24.129 +        } catch (URISyntaxException ex) {
  24.130 +            throw new IOException(ex);
  24.131 +        }
  24.132 +        
  24.133 +        JavaFileManager jfm = new ForwardingJavaFileManager<JavaFileManager>(sjfm) {
  24.134 +            @Override
  24.135 +            public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
  24.136 +                if (kind  == Kind.CLASS) {
  24.137 +                    final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  24.138 +
  24.139 +                    class2BAOS.put(className.replace('.', '/') + ".class", buffer);
  24.140 +                    return new SimpleJavaFileObject(sibling.toUri(), kind) {
  24.141 +                        @Override
  24.142 +                        public OutputStream openOutputStream() throws IOException {
  24.143 +                            return buffer;
  24.144 +                        }
  24.145 +                    };
  24.146 +                }
  24.147 +                
  24.148 +                if (kind == Kind.SOURCE) {
  24.149 +                    final String n = className.replace('.', '/') + ".java";
  24.150 +                    final URI un;
  24.151 +                    try {
  24.152 +                        un = new URI("mem://" + n);
  24.153 +                    } catch (URISyntaxException ex) {
  24.154 +                        throw new IOException(ex);
  24.155 +                    }
  24.156 +                    return new VirtFO(un/*sibling.toUri()*/, kind, n);
  24.157 +                }
  24.158 +                
  24.159 +                throw new IllegalStateException();
  24.160 +            }
  24.161 +
  24.162 +            @Override
  24.163 +            public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
  24.164 +                if (location == StandardLocation.SOURCE_PATH) {
  24.165 +                    if (packageName.equals(pkg)) {
  24.166 +                        return htmlFile;
  24.167 +                    }
  24.168 +                }
  24.169 +                
  24.170 +                return null;
  24.171 +            }
  24.172 +
  24.173 +            @Override
  24.174 +            public boolean isSameFile(FileObject a, FileObject b) {
  24.175 +                if (a instanceof VirtFO && b instanceof VirtFO) {
  24.176 +                    return ((VirtFO)a).getName().equals(((VirtFO)b).getName());
  24.177 +                }
  24.178 +                
  24.179 +                return super.isSameFile(a, b);
  24.180 +            }
  24.181 +
  24.182 +            class VirtFO extends SimpleJavaFileObject {
  24.183 +
  24.184 +                private final String n;
  24.185 +
  24.186 +                public VirtFO(URI uri, Kind kind, String n) {
  24.187 +                    super(uri, kind);
  24.188 +                    this.n = n;
  24.189 +                }
  24.190 +                private final ByteArrayOutputStream data = new ByteArrayOutputStream();
  24.191 +
  24.192 +                @Override
  24.193 +                public OutputStream openOutputStream() throws IOException {
  24.194 +                    return data;
  24.195 +                }
  24.196 +
  24.197 +                @Override
  24.198 +                public String getName() {
  24.199 +                    return n;
  24.200 +                }
  24.201 +
  24.202 +                @Override
  24.203 +                public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
  24.204 +                    data.close();
  24.205 +                    return new String(data.toByteArray());
  24.206 +                }
  24.207 +            }
  24.208 +        };
  24.209 +
  24.210 +        ToolProvider.getSystemJavaCompiler().getTask(null, jfm, this, /*XXX:*/Arrays.asList("-source", sourceLevel, "-target", "1.7"), null, Arrays.asList(file)).call();
  24.211 +
  24.212 +        Map<String, byte[]> result = new HashMap<String, byte[]>();
  24.213 +
  24.214 +        for (Map.Entry<String, ByteArrayOutputStream> e : class2BAOS.entrySet()) {
  24.215 +            result.put(e.getKey(), e.getValue().toByteArray());
  24.216 +        }
  24.217 +
  24.218 +        return result;
  24.219 +    }
  24.220 +
  24.221 +
  24.222 +    @Override
  24.223 +    public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
  24.224 +        errors.add(diagnostic);
  24.225 +    }
  24.226 +    private static String findPkg(String java) throws IOException {
  24.227 +        Pattern p = Pattern.compile("package\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}*;", Pattern.MULTILINE);
  24.228 +        Matcher m = p.matcher(java);
  24.229 +        if (!m.find()) {
  24.230 +            throw new IOException("Can't find package declaration in the java file");
  24.231 +        }
  24.232 +        String pkg = m.group(1);
  24.233 +        return pkg;
  24.234 +    }
  24.235 +    private static String findCls(String java) throws IOException {
  24.236 +        Pattern p = Pattern.compile("class\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}", Pattern.MULTILINE);
  24.237 +        Matcher m = p.matcher(java);
  24.238 +        if (!m.find()) {
  24.239 +            throw new IOException("Can't find package declaration in the java file");
  24.240 +        }
  24.241 +        String cls = m.group(1);
  24.242 +        return cls;
  24.243 +    }
  24.244 +
  24.245 +    String getHtml() {
  24.246 +        String fqn = "'" + pkg + '.' + cls + "'";
  24.247 +        return html.replace("'${fqn}'", fqn);
  24.248 +    }
  24.249 +
  24.250 +    void assertErrors() {
  24.251 +        assertFalse(getErrors().isEmpty(), "There are supposed to be some errors");
  24.252 +    }
  24.253 +
  24.254 +    void assertError(String expMsg) {
  24.255 +        StringBuilder sb = new StringBuilder();
  24.256 +        sb.append("Can't find ").append(expMsg).append(" among:");
  24.257 +        for (Diagnostic<? extends JavaFileObject> e : errors) {
  24.258 +            String msg = e.getMessage(Locale.US);
  24.259 +            if (msg.contains(expMsg)) {
  24.260 +                return;
  24.261 +            }
  24.262 +            sb.append("\n");
  24.263 +            sb.append(msg);
  24.264 +        }
  24.265 +        fail(sb.toString());
  24.266 +    }
  24.267 +}
    25.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    25.2 +++ b/geo/src/test/java/org/apidesign/html/geo/impl/GeoProcessorTest.java	Thu Jul 18 15:39:56 2013 +0200
    25.3 @@ -0,0 +1,92 @@
    25.4 +/**
    25.5 + * HTML via Java(tm) Language Bindings
    25.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    25.7 + *
    25.8 + * This program is free software: you can redistribute it and/or modify
    25.9 + * it under the terms of the GNU General Public License as published by
   25.10 + * the Free Software Foundation, version 2 of the License.
   25.11 + *
   25.12 + * This program is distributed in the hope that it will be useful,
   25.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   25.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   25.15 + * GNU General Public License for more details. apidesign.org
   25.16 + * designates this particular file as subject to the
   25.17 + * "Classpath" exception as provided by apidesign.org
   25.18 + * in the License file that accompanied this code.
   25.19 + *
   25.20 + * You should have received a copy of the GNU General Public License
   25.21 + * along with this program. Look for COPYING file in the top folder.
   25.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   25.23 + */
   25.24 +package org.apidesign.html.geo.impl;
   25.25 +
   25.26 +import java.io.IOException;
   25.27 +import org.testng.annotations.Test;
   25.28 +
   25.29 +/** Test whether the annotation processor detects errors correctly.
   25.30 + *
   25.31 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   25.32 + */
   25.33 +public class GeoProcessorTest {
   25.34 +    
   25.35 +    public GeoProcessorTest() {
   25.36 +    }
   25.37 +
   25.38 +    @Test public void onLocationMethodHasToTakePositionParameter() throws IOException {
   25.39 +        Compile res = Compile.create("", "package x;\n"
   25.40 +            + "class UseOnLocation {\n"
   25.41 +            + "  @net.java.html.geo.OnLocation\n"
   25.42 +            + "  public static void cantCallMe() {}\n"
   25.43 +            + "}\n"
   25.44 +        );
   25.45 +        res.assertErrors();
   25.46 +        res.assertError("one net.java.html.geo.Position argument");
   25.47 +    }
   25.48 +    
   25.49 +    @Test public void onLocationMethodCannotBePrivate() throws IOException {
   25.50 +        Compile res = Compile.create("", "package x;\n"
   25.51 +            + "class UseOnLocation {\n"
   25.52 +            + "  @net.java.html.geo.OnLocation\n"
   25.53 +            + "  private static void cantCallMe(net.java.html.geo.Position p) {}\n"
   25.54 +            + "}\n"
   25.55 +        );
   25.56 +        res.assertErrors();
   25.57 +        res.assertError("cannot be private");
   25.58 +    }
   25.59 +    
   25.60 +    @Test public void onErrorHasToExist() throws IOException {
   25.61 +        Compile res = Compile.create("", "package x;\n"
   25.62 +            + "class UseOnLocation {\n"
   25.63 +            + "  @net.java.html.geo.OnLocation(onError=\"doesNotExist\")\n"
   25.64 +            + "  static void cantCallMe(net.java.html.geo.Position p) {}\n"
   25.65 +            + "}\n"
   25.66 +        );
   25.67 +        res.assertErrors();
   25.68 +        res.assertError("not find doesNotExist");
   25.69 +    }
   25.70 +
   25.71 +    @Test public void onErrorWouldHaveToBeStatic() throws IOException {
   25.72 +        Compile res = Compile.create("", "package x;\n"
   25.73 +            + "class UseOnLocation {\n"
   25.74 +            + "  @net.java.html.geo.OnLocation(onError=\"notStatic\")\n"
   25.75 +            + "  static void cantCallMe(net.java.html.geo.Position p) {}\n"
   25.76 +            + "  void notStatic(Exception e) {}\n"
   25.77 +            + "}\n"
   25.78 +        );
   25.79 +        res.assertErrors();
   25.80 +        res.assertError("have to be static");
   25.81 +    }
   25.82 +
   25.83 +    @Test public void onErrorMustAcceptExceptionArgument() throws IOException {
   25.84 +        Compile res = Compile.create("", "package x;\n"
   25.85 +            + "class UseOnLocation {\n"
   25.86 +            + "  @net.java.html.geo.OnLocation(onError=\"notStatic\")\n"
   25.87 +            + "  static void cantCallMe(net.java.html.geo.Position p) {}\n"
   25.88 +            + "  static void notStatic(java.io.IOException e) {}\n"
   25.89 +            + "}\n"
   25.90 +        );
   25.91 +        res.assertErrors();
   25.92 +        res.assertError("take one Exception arg");
   25.93 +    }
   25.94 +    
   25.95 +}
    26.1 --- a/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java	Sat Jun 29 07:01:58 2013 +0200
    26.2 +++ b/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java	Thu Jul 18 15:39:56 2013 +0200
    26.3 @@ -38,10 +38,59 @@
    26.4      @Property(name="results", type=String.class, array = true),
    26.5      @Property(name="callbackCount", type=int.class),
    26.6      @Property(name="people", type=PersonImpl.class, array = true),
    26.7 -    @Property(name="enabled", type=boolean.class)
    26.8 +    @Property(name="enabled", type=boolean.class),
    26.9 +    @Property(name="latitude", type=double.class)
   26.10  }) 
   26.11  public final class KnockoutTest {
   26.12      
   26.13 +    @KOTest public void modifyValueAssertChangeInModelOnDouble() throws Throwable {
   26.14 +        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   26.15 +            "Latitude: <input id='input' data-bind=\"value: latitude\"></input>\n"
   26.16 +        );
   26.17 +        try {
   26.18 +
   26.19 +            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   26.20 +            m.setLatitude(50.5);
   26.21 +            m.applyBindings();
   26.22 +
   26.23 +            String v = getSetInput(null);
   26.24 +            assert "50.5".equals(v) : "Value is really 50.5: " + v;
   26.25 +
   26.26 +            getSetInput("49.5");
   26.27 +            triggerEvent("input", "change");
   26.28 +
   26.29 +            assert 49.5 == m.getLatitude() : "Double property updated: " + m.getLatitude();
   26.30 +        } catch (Throwable t) {
   26.31 +            throw t;
   26.32 +        } finally {
   26.33 +            Utils.exposeHTML(KnockoutTest.class, "");
   26.34 +        }
   26.35 +    }
   26.36 +    
   26.37 +    @KOTest public void modifyValueAssertChangeInModelOnBoolean() throws Throwable {
   26.38 +        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   26.39 +            "Latitude: <input id='input' data-bind=\"value: enabled\"></input>\n"
   26.40 +        );
   26.41 +        try {
   26.42 +
   26.43 +            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   26.44 +            m.setEnabled(true);
   26.45 +            m.applyBindings();
   26.46 +
   26.47 +            String v = getSetInput(null);
   26.48 +            assert "true".equals(v) : "Value is really true: " + v;
   26.49 +
   26.50 +            getSetInput("false");
   26.51 +            triggerEvent("input", "change");
   26.52 +
   26.53 +            assert false == m.isEnabled(): "Boolean property updated: " + m.isEnabled();
   26.54 +        } catch (Throwable t) {
   26.55 +            throw t;
   26.56 +        } finally {
   26.57 +            Utils.exposeHTML(KnockoutTest.class, "");
   26.58 +        }
   26.59 +    }
   26.60 +    
   26.61      @KOTest public void modifyValueAssertChangeInModel() throws Exception {
   26.62          Object exp = Utils.exposeHTML(KnockoutTest.class, 
   26.63              "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
    27.1 --- a/json/src/main/java/org/apidesign/html/json/impl/JSON.java	Sat Jun 29 07:01:58 2013 +0200
    27.2 +++ b/json/src/main/java/org/apidesign/html/json/impl/JSON.java	Thu Jul 18 15:39:56 2013 +0200
    27.3 @@ -20,7 +20,6 @@
    27.4   */
    27.5  package org.apidesign.html.json.impl;
    27.6  
    27.7 -import org.apidesign.html.context.impl.CtxAccssr;
    27.8  import java.io.IOException;
    27.9  import java.io.InputStream;
   27.10  import java.util.HashMap;
   27.11 @@ -101,6 +100,41 @@
   27.12          return aClass.cast(o);
   27.13      }
   27.14  
   27.15 +    public static <T> T extractValue(Class<T> type, Object val) {
   27.16 +        if (Number.class.isAssignableFrom(type)) {
   27.17 +            val = numberValue(val);
   27.18 +        }
   27.19 +        if (Boolean.class == type) {
   27.20 +            val = boolValue(val);
   27.21 +        }
   27.22 +        return type.cast(val);
   27.23 +    }
   27.24 +    
   27.25 +    public static String stringValue(Object val) {
   27.26 +        return (String)val;
   27.27 +    }
   27.28 +
   27.29 +    public static Number numberValue(Object val) {
   27.30 +        if (val instanceof String) {
   27.31 +            try {
   27.32 +                return Double.valueOf((String)val);
   27.33 +            } catch (NumberFormatException ex) {
   27.34 +                return Double.NaN;
   27.35 +            }
   27.36 +        }
   27.37 +        return (Number)val;
   27.38 +    }
   27.39 +
   27.40 +    public static Character charValue(Object val) {
   27.41 +        return (Character)val;
   27.42 +    }
   27.43 +
   27.44 +    public static Boolean boolValue(Object val) {
   27.45 +        if (val instanceof String) {
   27.46 +            return Boolean.parseBoolean((String)val);
   27.47 +        }
   27.48 +        return Boolean.TRUE.equals(val);
   27.49 +    }
   27.50      
   27.51      public static void loadJSON(
   27.52          BrwsrCtx c, Runnable whenDone, Object[] result, 
    28.1 --- a/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java	Sat Jun 29 07:01:58 2013 +0200
    28.2 +++ b/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java	Thu Jul 18 15:39:56 2013 +0200
    28.3 @@ -220,14 +220,12 @@
    28.4                  for (int i = 0; i < propsGetSet.size(); i += 5) {
    28.5                      final String set = propsGetSet.get(i + 2);
    28.6                      String tn = propsGetSet.get(i + 4);
    28.7 -                    if (processingEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_6) <= 0) {
    28.8 -                        String btn = findBoxedType(tn);
    28.9 -                        if (btn != null) {
   28.10 -                            tn = btn;
   28.11 -                        }
   28.12 +                    String btn = findBoxedType(tn);
   28.13 +                    if (btn != null) {
   28.14 +                        tn = btn;
   28.15                      }
   28.16                      if (set != null) {
   28.17 -                        w.append("        case " + (i / 5) + ": data." + strip(set) + "((" + tn + ")value); return;\n");
   28.18 +                        w.append("        case " + (i / 5) + ": data." + strip(set) + "(org.apidesign.html.json.impl.JSON.extractValue(" + tn + ".class, value)); return;\n");
   28.19                      }
   28.20                  }
   28.21                  w.append("      }\n");
   28.22 @@ -296,10 +294,10 @@
   28.23                          } else if (isEnum[0]) {
   28.24                              w.append("        this.prop_").append(pn);
   28.25                              w.append(".add(e == null ? null : ");
   28.26 -                            w.append(type).append(".valueOf((String)e));\n");
   28.27 +                            w.append(type).append(".valueOf(org.apidesign.html.json.impl.JSON.stringValue(e)));\n");
   28.28                          } else {
   28.29                              if (isPrimitive(type)) {
   28.30 -                                w.append("        this.prop_").append(pn).append(".add(((Number)e).");
   28.31 +                                w.append("        this.prop_").append(pn).append(".add(org.apidesign.html.json.impl.JSON.numberValue(e).");
   28.32                                  w.append(type).append("Value());\n");
   28.33                              } else {
   28.34                                  w.append("        this.prop_").append(pn).append(".add((");
   28.35 @@ -312,18 +310,18 @@
   28.36                          if (isEnum[0]) {
   28.37                              w.append("    this.prop_").append(pn);
   28.38                              w.append(" = ret[" + cnt + "] == null ? null : ");
   28.39 -                            w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n");
   28.40 +                            w.append(type).append(".valueOf(org.apidesign.html.json.impl.JSON.stringValue(ret[" + cnt + "]));\n");
   28.41                          } else if (isPrimitive(type)) {
   28.42                              w.append("    this.prop_").append(pn);
   28.43                              w.append(" = ret[" + cnt + "] == null ? ");
   28.44                              if ("char".equals(type)) {
   28.45 -                                w.append("0 : ((Character)");
   28.46 +                                w.append("0 : (org.apidesign.html.json.impl.JSON.charValue(");
   28.47                              } else if ("boolean".equals(type)) {
   28.48 -                                w.append("false : ((Boolean)");
   28.49 +                                w.append("false : (org.apidesign.html.json.impl.JSON.boolValue(");
   28.50                              } else {
   28.51 -                                w.append("0 : ((Number)");
   28.52 +                                w.append("0 : (org.apidesign.html.json.impl.JSON.numberValue(");
   28.53                              }
   28.54 -                            w.append("ret[" + cnt + "]).");
   28.55 +                            w.append("ret[" + cnt + "])).");
   28.56                              w.append(type).append("Value();\n");
   28.57                          } else if (isModel[0]) {
   28.58                              w.append("    this.prop_").append(pn).append(" = org.apidesign.html.json.impl.JSON.read");
   28.59 @@ -647,7 +645,7 @@
   28.60              }
   28.61              String n = e.getSimpleName().toString();
   28.62              body.append("  private void ").append(n).append("(Object data, Object ev) {\n");
   28.63 -            body.append("    ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   28.64 +            body.append("    ").append(((TypeElement)clazz).getQualifiedName()).append(".").append(n).append("(");
   28.65              body.append(wrapParams(e, null, className, "ev", "data"));
   28.66              body.append(");\n");
   28.67              body.append("  }\n");
    29.1 --- a/json/src/test/java/net/java/html/json/PersonImpl.java	Sat Jun 29 07:01:58 2013 +0200
    29.2 +++ b/json/src/test/java/net/java/html/json/PersonImpl.java	Thu Jul 18 15:39:56 2013 +0200
    29.3 @@ -59,6 +59,8 @@
    29.4          @Property(array = true, name = "age", type = int.class),
    29.5          @Property(array = true, name = "sex", type = Sex.class)
    29.6      })
    29.7 -    public class PeopleImpl {
    29.8 +    public static class PeopleImpl {
    29.9 +        @Function static void inInnerClass(People p) {
   29.10 +        }
   29.11      }
   29.12  }
    30.1 --- a/ko-archetype/src/main/resources/archetype-resources/src/main/java/Main.java	Sat Jun 29 07:01:58 2013 +0200
    30.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/main/java/Main.java	Thu Jul 18 15:39:56 2013 +0200
    30.3 @@ -12,5 +12,6 @@
    30.4              loadClass(TwitterClient.class).
    30.5              invoke("initialize", args).
    30.6              showAndWait();
    30.7 +        System.exit(0);
    30.8      }
    30.9  }
    31.1 --- a/pom.xml	Sat Jun 29 07:01:58 2013 +0200
    31.2 +++ b/pom.xml	Thu Jul 18 15:39:56 2013 +0200
    31.3 @@ -25,6 +25,7 @@
    31.4      <module>context</module>
    31.5      <module>boot</module>
    31.6      <module>boot-fx</module>
    31.7 +    <module>geo</module>
    31.8    </modules>
    31.9    <licenses>
   31.10        <license>