boot/src/main/java/org/netbeans/html/boot/spi/Fn.java
author Jaroslav Tulach <jtulach@netbeans.org>
Tue, 26 Aug 2014 18:13:30 +0200
changeset 838 bdc3d696dd4a
parent 716 boot/src/main/java/org/apidesign/html/boot/spi/Fn.java@0654466b2273
child 898 18e17d0cd066
child 900 2ee22312e414
permissions -rw-r--r--
During the API review process (bug 246133) the reviewers decided that in order to include html4j to NetBeans Platform, we need to stop using org.apidesign namespace and switch to NetBeans one. Repackaging all SPI packages into org.netbeans.html.smthng.spi.
jaroslav@123
     1
/**
jaroslav@358
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
jaroslav@123
     3
 *
jaroslav@551
     4
 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
jaroslav@123
     5
 *
jaroslav@358
     6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
jaroslav@358
     7
 * Other names may be trademarks of their respective owners.
jaroslav@123
     8
 *
jaroslav@358
     9
 * The contents of this file are subject to the terms of either the GNU
jaroslav@358
    10
 * General Public License Version 2 only ("GPL") or the Common
jaroslav@358
    11
 * Development and Distribution License("CDDL") (collectively, the
jaroslav@358
    12
 * "License"). You may not use this file except in compliance with the
jaroslav@358
    13
 * License. You can obtain a copy of the License at
jaroslav@358
    14
 * http://www.netbeans.org/cddl-gplv2.html
jaroslav@358
    15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
jaroslav@358
    16
 * specific language governing permissions and limitations under the
jaroslav@358
    17
 * License.  When distributing the software, include this License Header
jaroslav@358
    18
 * Notice in each file and include the License file at
jaroslav@358
    19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
jaroslav@358
    20
 * particular file as subject to the "Classpath" exception as provided
jaroslav@358
    21
 * by Oracle in the GPL Version 2 section of the License file that
jaroslav@358
    22
 * accompanied this code. If applicable, add the following below the
jaroslav@358
    23
 * License Header, with the fields enclosed by brackets [] replaced by
jaroslav@358
    24
 * your own identifying information:
jaroslav@358
    25
 * "Portions Copyrighted [year] [name of copyright owner]"
jaroslav@358
    26
 *
jaroslav@358
    27
 * Contributor(s):
jaroslav@358
    28
 *
jaroslav@358
    29
 * The Original Software is NetBeans. The Initial Developer of the Original
jaroslav@551
    30
 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
jaroslav@358
    31
 *
jaroslav@358
    32
 * If you wish your version of this file to be governed by only the CDDL
jaroslav@358
    33
 * or only the GPL Version 2, indicate your decision by adding
jaroslav@358
    34
 * "[Contributor] elects to include this software in this distribution
jaroslav@358
    35
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
jaroslav@358
    36
 * single choice of license, a recipient has the option to distribute
jaroslav@358
    37
 * your version of this file under either the CDDL, the GPL Version 2 or
jaroslav@358
    38
 * to extend the choice of license to its licensees as provided above.
jaroslav@358
    39
 * However, if you add GPL Version 2 code and therefore, elected the GPL
jaroslav@358
    40
 * Version 2 license, then the option applies only if the new code is
jaroslav@358
    41
 * made subject to such option by the copyright holder.
jaroslav@123
    42
 */
jtulach@838
    43
package org.netbeans.html.boot.spi;
jaroslav@123
    44
jaroslav@322
    45
import java.io.Closeable;
jtulach@680
    46
import java.io.IOException;
jaroslav@349
    47
import java.io.InputStream;
jaroslav@349
    48
import java.io.InputStreamReader;
jaroslav@163
    49
import java.io.Reader;
jaroslav@123
    50
import java.net.URL;
jaroslav@349
    51
import java.util.HashMap;
jaroslav@349
    52
import java.util.HashSet;
jaroslav@349
    53
import java.util.Map;
jaroslav@349
    54
import java.util.Set;
jaroslav@431
    55
import java.util.concurrent.Executor;
jaroslav@322
    56
import net.java.html.js.JavaScriptBody;
jaroslav@362
    57
import org.netbeans.html.boot.impl.FnContext;
jaroslav@123
    58
jaroslav@289
    59
/** Represents single JavaScript function that can be invoked. 
jaroslav@289
    60
 * Created via {@link Presenter#defineFn(java.lang.String, java.lang.String...)}.
jaroslav@123
    61
 *
jtulach@655
    62
 * @author Jaroslav Tulach
jaroslav@123
    63
 */
jaroslav@123
    64
public abstract class Fn {
jaroslav@288
    65
    private final Presenter presenter;
jaroslav@288
    66
    
jaroslav@289
    67
    /**
jaroslav@289
    68
     * @deprecated Ineffective as of 0.6. 
jtulach@838
    69
     * Provide a presenter via {@link #Fn(org.netbeans.html.boot.spi.Fn.Presenter)}
jaroslav@289
    70
     * constructor
jaroslav@289
    71
     */
jaroslav@289
    72
    @Deprecated
jaroslav@289
    73
    protected Fn() {
jaroslav@289
    74
        this(null);
jaroslav@289
    75
    }
jaroslav@289
    76
    
jaroslav@289
    77
    /** Creates new function object and associates it with given presenter.
jaroslav@289
    78
     * 
jaroslav@289
    79
     * @param presenter the browser presenter associated with this function
jaroslav@289
    80
     * @since 0.6 
jaroslav@289
    81
     */
jaroslav@288
    82
    protected Fn(Presenter presenter) {
jaroslav@288
    83
        this.presenter = presenter;
jaroslav@288
    84
    }
jaroslav@289
    85
jaroslav@289
    86
    /** True, if currently active presenter is the same as presenter this
jtulach@838
    87
     * function has been created for via {@link #Fn(org.netbeans.html.boot.spi.Fn.Presenter)}.
jaroslav@289
    88
     * 
jaroslav@289
    89
     * @return true, if proper presenter is used
jaroslav@289
    90
     */
jaroslav@288
    91
    public final boolean isValid() {
jaroslav@451
    92
        return presenter != null && FnContext.currentPresenter(false) == presenter;
jaroslav@288
    93
    }
jaroslav@288
    94
    
jaroslav@323
    95
    /** Helper method to check if the provided instance is valid function.
jaroslav@323
    96
     * Checks if the parameter is non-null and if so, does {@link #isValid()}
jaroslav@323
    97
     * check.
jaroslav@323
    98
     * 
jaroslav@323
    99
     * @param fnOrNull function or <code>null</code>
jaroslav@323
   100
     * @return true if the parameter is non-null and valid
jaroslav@323
   101
     * @since 0.7
jaroslav@323
   102
     */
jaroslav@323
   103
    public static boolean isValid(Fn fnOrNull) {
jaroslav@323
   104
        return fnOrNull != null && fnOrNull.isValid();
jaroslav@323
   105
    }
jaroslav@323
   106
jaroslav@323
   107
    /** Helper method to find current presenter and ask it to define new
jaroslav@323
   108
     * function by calling {@link Presenter#defineFn(java.lang.String, java.lang.String...)}.
jaroslav@323
   109
     * 
jaroslav@323
   110
     * @param caller the class who wishes to define the function
jaroslav@323
   111
     * @param code the body of the function (can reference <code>this</code> and <code>names</code> variables)
jaroslav@323
   112
     * @param names names of individual parameters
jaroslav@323
   113
     * @return the function object that can be {@link Fn#invoke(java.lang.Object, java.lang.Object...) invoked}
jaroslav@451
   114
     *    - can return <code>null</code> if there is {@link #activePresenter() no presenter}
jaroslav@323
   115
     * @since 0.7
jaroslav@323
   116
     */
jaroslav@323
   117
    public static Fn define(Class<?> caller, String code, String... names) {
jaroslav@451
   118
        final Presenter p = FnContext.currentPresenter(false);
jaroslav@451
   119
        return p == null ? null : p.defineFn(code, names);
jaroslav@323
   120
    }
jaroslav@323
   121
    
jaroslav@349
   122
    private static final Map<String,Set<Presenter>> LOADED = new HashMap<String, Set<Presenter>>();
jaroslav@427
   123
    
jaroslav@427
   124
    /** Wraps function to ensure that the script represented by <code>resource</code>
jaroslav@427
   125
     * gets loaded into the browser environment before the function <code>fn</code>
jaroslav@427
   126
     * is executed.
jaroslav@427
   127
     * 
jaroslav@560
   128
     * @param fn original function to call (if <code>null</code> returns <code>null</code>)
jaroslav@427
   129
     * @param caller the class who wishes to define/call the function
jaroslav@427
   130
     * @param resource resources (accessible via {@link ClassLoader#getResource(java.lang.String)}) 
jaroslav@427
   131
     *   with a <em>JavaScript</em> that is supposed to loaded into the browser
jaroslav@427
   132
     *   environment
jaroslav@427
   133
     * @return function that ensures the script is loaded and then delegates
jaroslav@560
   134
     *   to <code>fn</code>. Returns <code>null</code> if the input <code>fn</code> is null
jaroslav@427
   135
     * @since 0.7
jaroslav@427
   136
     */
jaroslav@349
   137
    public static Fn preload(final Fn fn, final Class<?> caller, final String resource) {
jaroslav@560
   138
        if (fn == null) {
jaroslav@560
   139
            return null;
jaroslav@560
   140
        }
jaroslav@555
   141
        return new Fn(fn.presenter()) {
jaroslav@349
   142
            @Override
jaroslav@349
   143
            public Object invoke(Object thiz, Object... args) throws Exception {
jaroslav@577
   144
                loadResource();
jaroslav@577
   145
                return fn.invoke(thiz, args);
jaroslav@577
   146
            }
jaroslav@577
   147
jaroslav@577
   148
            @Override
jaroslav@577
   149
            public void invokeLater(Object thiz, Object... args) throws Exception {
jaroslav@577
   150
                loadResource();
jaroslav@577
   151
                fn.invokeLater(thiz, args);
jaroslav@577
   152
            }
jaroslav@577
   153
            
jaroslav@577
   154
            private void loadResource() throws Exception {
jaroslav@560
   155
                Presenter p = presenter();
jaroslav@560
   156
                if (p == null) {
jaroslav@560
   157
                    p = FnContext.currentPresenter(false);
jaroslav@349
   158
                }
jaroslav@560
   159
                if (p != null) {
jaroslav@560
   160
                    Set<Presenter> there = LOADED.get(resource);
jaroslav@560
   161
                    if (there == null) {
jaroslav@560
   162
                        there = new HashSet<Presenter>();
jaroslav@560
   163
                        LOADED.put(resource, there);
jaroslav@560
   164
                    }
jaroslav@560
   165
                    if (there.add(p)) {
jtulach@680
   166
                        final ClassLoader l = caller.getClassLoader();
jtulach@680
   167
                        InputStream is = l.getResourceAsStream(resource);
jtulach@680
   168
                        if (is == null && resource.startsWith("/")) {
jtulach@680
   169
                            is = l.getResourceAsStream(resource.substring(1));
jtulach@680
   170
                        }
jtulach@680
   171
                        if (is == null) {
jtulach@680
   172
                            throw new IOException("Cannot find " + resource + " in " + l);
jtulach@680
   173
                        }
jaroslav@560
   174
                        try {
jaroslav@560
   175
                            InputStreamReader r = new InputStreamReader(is, "UTF-8");
jaroslav@560
   176
                            p.loadScript(r);
jaroslav@560
   177
                        } finally {
jaroslav@560
   178
                            is.close();
jaroslav@560
   179
                        }
jaroslav@349
   180
                    }
jaroslav@349
   181
                }
jaroslav@349
   182
            }
jaroslav@349
   183
        };
jaroslav@349
   184
    }
jaroslav@577
   185
jaroslav@349
   186
    
jaroslav@322
   187
    /** The currently active presenter.
jaroslav@322
   188
     * 
jaroslav@322
   189
     * @return the currently active presenter or <code>null</code>
jaroslav@322
   190
     * @since 0.7
jaroslav@322
   191
     */
jaroslav@322
   192
    public static Presenter activePresenter() {
jaroslav@434
   193
        return FnContext.currentPresenter(false);
jaroslav@322
   194
    }
jaroslav@322
   195
    
jaroslav@716
   196
    /** Activates given presenter. Used to associate the native 
jaroslav@716
   197
     * JavaScript code specified by 
jaroslav@716
   198
     * {@link JavaScriptBody} annotation with certain presenter:
jaroslav@322
   199
     * <pre>
jaroslav@322
   200
     * try ({@link Closeable} c = Fn.activate(presenter)) {
jaroslav@322
   201
     *   doCallsInPresenterContext();
jaroslav@322
   202
     * }
jaroslav@322
   203
     * </pre>
jaroslav@322
   204
     * 
jaroslav@322
   205
     * @param p the presenter that should be active until closable is closed
jaroslav@322
   206
     * @return the closable to close
jaroslav@322
   207
     * @since 0.7
jaroslav@322
   208
     */
jaroslav@322
   209
    public static Closeable activate(Presenter p) {
jaroslav@322
   210
        return FnContext.activate(p);
jaroslav@322
   211
    }
jaroslav@322
   212
    
jaroslav@289
   213
    /** Invokes the defined function with specified <code>this</code> and
jaroslav@289
   214
     * appropriate arguments.
jaroslav@289
   215
     * 
jaroslav@289
   216
     * @param thiz the meaning of <code>this</code> inside of the JavaScript
jaroslav@289
   217
     *   function - can be <code>null</code>
jaroslav@289
   218
     * @param args arguments for the function
jaroslav@289
   219
     * @return return value from the function
jaroslav@289
   220
     * @throws Exception if something goes wrong, as exception may be thrown
jaroslav@289
   221
     */
jaroslav@289
   222
    public abstract Object invoke(Object thiz, Object... args) throws Exception;
jaroslav@593
   223
jaroslav@593
   224
    /** Invokes the defined function with specified <code>this</code> and
jaroslav@593
   225
     * appropriate arguments asynchronously. The invocation may be 
jaroslav@593
   226
     * happen <em>"later"</em>.
jaroslav@593
   227
     * 
jaroslav@593
   228
     * @param thiz the meaning of <code>this</code> inside of the JavaScript
jaroslav@593
   229
     *   function - can be <code>null</code>
jaroslav@593
   230
     * @param args arguments for the function
jaroslav@593
   231
     * @throws Exception if something goes wrong, as exception may be thrown
jaroslav@593
   232
     * @since 0.7.6
jaroslav@593
   233
     */
jaroslav@593
   234
    public void invokeLater(Object thiz, Object... args) throws Exception {
jaroslav@593
   235
        invoke(this, args);
jaroslav@593
   236
    }
jaroslav@429
   237
    
jaroslav@429
   238
    /** Provides the function implementation access to the presenter provided
jtulach@838
   239
     * in {@link #Fn(org.netbeans.html.boot.spi.Fn.Presenter) the constructor}.
jaroslav@429
   240
     * 
jaroslav@530
   241
     * @return presenter passed in the constructor (may be, but should not be <code>null</code>)
jaroslav@429
   242
     * @since 0.7
jaroslav@429
   243
     */
jaroslav@429
   244
    protected final Presenter presenter() {
jaroslav@429
   245
        return presenter;
jaroslav@429
   246
    }
jaroslav@289
   247
jaroslav@289
   248
    /** The representation of a <em>presenter</em> - usually a browser window.
jaroslav@315
   249
     * Should be provided by a library included in the application and registered
jaroslav@315
   250
     * in <code>META-INF/services</code>, for example with
jaroslav@315
   251
     * <code>@ServiceProvider(service = Fn.Presenter.class)</code> annotation.
jaroslav@431
   252
     * <p>
jaroslav@431
   253
     * Since 0.7 a presenter may implement {@link Executor} interface, in case
jaroslav@431
   254
     * it supports single threaded execution environment. The executor's
jaroslav@431
   255
     * {@link Executor#execute(java.lang.Runnable)} method is then supposed
jaroslav@431
   256
     * to invoke the runnable immediately (in case we are on the right thread
jaroslav@431
   257
     * already) or return and asynchronously invoke the runnable later on the
jaroslav@431
   258
     * right thread (if we are on wrong thread).
jaroslav@289
   259
     */
jaroslav@127
   260
    public interface Presenter {
jaroslav@289
   261
        /** Creates new function with given parameter names and provided body.
jaroslav@289
   262
         * 
jaroslav@289
   263
         * @param code the body of the function. Can refer to variables named
jaroslav@289
   264
         *   as <code>names</code>
jaroslav@289
   265
         * @param names names of parameters of the function - these will be 
jaroslav@289
   266
         *   available when the <code>code</code> body executes
jaroslav@289
   267
         * 
jaroslav@289
   268
         * @return function that can be later invoked
jaroslav@289
   269
         */
jaroslav@127
   270
        public Fn defineFn(String code, String... names);
jaroslav@289
   271
        
jaroslav@289
   272
        /** Opens the browser, loads provided page and when the
jaroslav@289
   273
         * page is ready, it calls back to the provider runnable.
jaroslav@289
   274
         * 
jaroslav@289
   275
         * @param page the URL for the page to display
jaroslav@289
   276
         * @param onPageLoad callback when the page is ready
jaroslav@289
   277
         */
jaroslav@128
   278
        public void displayPage(URL page, Runnable onPageLoad);
jaroslav@289
   279
        
jaroslav@289
   280
        /** Loads a script into the browser JavaScript interpreter and 
jaroslav@289
   281
         * executes it.
jaroslav@289
   282
         * @param code the script to execute
jaroslav@289
   283
         * @throws Exception if something goes wrong, throw an exception
jaroslav@289
   284
         */
jaroslav@163
   285
        public void loadScript(Reader code) throws Exception;
jaroslav@123
   286
    }
jaroslav@430
   287
    
jaroslav@430
   288
    /** Additional interface to be implemented by {@link Presenter}s that
jaroslav@430
   289
     * wish to control what objects are passed into the JavaScript virtual 
jaroslav@430
   290
     * machine.
jaroslav@430
   291
     * <p>
jaroslav@430
   292
     * If a JavaScript engine makes callback to Java method that returns 
jaroslav@430
   293
     * a value, the {@link #toJavaScript(java.lang.Object)} method is
jaroslav@430
   294
     * consulted to convert the Java value to something reasonable inside
jaroslav@430
   295
     * JavaScript VM.
jaroslav@430
   296
     * <p>
jaroslav@430
   297
     * <em>Note:</em> The implementation based on <em>JavaFX</em> <code>WebView</code>
jaroslav@430
   298
     * uses this interface to convert Java arrays to JavaScript ones.
jaroslav@430
   299
     * 
jaroslav@430
   300
     * @see Presenter
jaroslav@430
   301
     * @since 0.7
jaroslav@430
   302
     */
jaroslav@430
   303
    public interface ToJavaScript {
jaroslav@430
   304
        /** Convert a Java return value into some object suitable for
jaroslav@430
   305
         * JavaScript virtual machine.
jaroslav@430
   306
         * 
jaroslav@430
   307
         * @param toReturn the Java object to be returned
jaroslav@430
   308
         * @return the replacement value to return instead
jaroslav@430
   309
         */
jaroslav@430
   310
        public Object toJavaScript(Object toReturn);
jaroslav@430
   311
    }
jaroslav@446
   312
    
jaroslav@446
   313
    /** Additional interface to be implemented by {@link Presenter}s that
jaroslav@446
   314
     * need to convert JavaScript object (usually array) to Java object 
jaroslav@446
   315
     * when calling back from JavaScript to Java.
jaroslav@446
   316
     * <p>
jaroslav@446
   317
     * <em>Note:</em> The implementation based on <em>JavaFX</em>
jaroslav@446
   318
     * <code>WebView</code> uses this interface to convert JavaScript arrays to
jaroslav@446
   319
     * Java ones.
jaroslav@446
   320
      * 
jaroslav@446
   321
     * @since 0.7
jaroslav@446
   322
     */
jaroslav@446
   323
    public interface FromJavaScript {
jaroslav@446
   324
        /** Convert a JavaScript object into suitable Java representation
jaroslav@446
   325
         * before a Java method is called with this object as an argument.
jaroslav@446
   326
         * 
jaroslav@446
   327
         * @param js the JavaScript object
jaroslav@446
   328
         * @return replacement object for 
jaroslav@446
   329
         */
jaroslav@446
   330
        public Object toJava(Object js);
jaroslav@446
   331
    }
jaroslav@123
   332
}