boot/src/main/java/net/java/html/boot/BrowserBuilder.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Mon, 16 Dec 2013 16:59:43 +0100
branchnetbeans
changeset 362 92fb71afdc0e
parent 361 700087d2a5d3
child 365 5c93ad8c7a15
permissions -rw-r--r--
Moving implementation classes into org.netbeans.html namespace
jaroslav@123
     1
/**
jaroslav@358
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
jaroslav@123
     3
 *
jaroslav@358
     4
 * Copyright 1997-2010 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@358
    30
 * Software is Oracle. Portions Copyright 2013-2013 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
 */
jaroslav@123
    43
package net.java.html.boot;
jaroslav@123
    44
jaroslav@202
    45
import java.io.File;
jaroslav@127
    46
import java.io.IOException;
jaroslav@156
    47
import java.lang.reflect.Method;
jaroslav@148
    48
import java.net.MalformedURLException;
jaroslav@127
    49
import java.net.URL;
jaroslav@156
    50
import java.util.Arrays;
jaroslav@127
    51
import java.util.Collection;
jaroslav@127
    52
import java.util.Enumeration;
jaroslav@127
    53
import java.util.ServiceLoader;
jaroslav@156
    54
import java.util.logging.Level;
jaroslav@156
    55
import java.util.logging.Logger;
jaroslav@166
    56
import net.java.html.js.JavaScriptBody;
jaroslav@362
    57
import org.netbeans.html.boot.impl.FnUtils;
jaroslav@127
    58
import org.apidesign.html.boot.spi.Fn;
jaroslav@362
    59
import org.netbeans.html.boot.impl.FindResources;
jaroslav@362
    60
import org.netbeans.html.boot.impl.FnContext;
jaroslav@127
    61
jaroslav@166
    62
/** Use this builder to launch your Java/HTML based application. Typical
jaroslav@166
    63
 * usage in a main method of your application looks like this: 
jaroslav@166
    64
 * <pre>
jaroslav@166
    65
 * 
jaroslav@166
    66
 * <b>public static void</b> <em>main</em>(String... args) {
jaroslav@166
    67
 *     BrowserBuilder.{@link #newBrowser}.
jaroslav@166
    68
 *          {@link #loadClass(java.lang.Class) loadClass(YourMain.class)}.
jaroslav@166
    69
 *          {@link #loadPage(java.lang.String) loadPage("index.html")}.
jaroslav@166
    70
 *          {@link #invoke(java.lang.String, java.lang.String[]) invoke("initialized", args)}.
jaroslav@166
    71
 *          {@link #showAndWait()};
jaroslav@166
    72
 *     System.exit(0);
jaroslav@166
    73
 * }
jaroslav@166
    74
 * </pre>
jaroslav@166
    75
 * The above will load <code>YourMain</code> class via
jaroslav@166
    76
 * a special classloader, it will locate an <code>index.html</code> (relative
jaroslav@166
    77
 * to <code>YourMain</code> class) and show it in a browser window. When the
jaroslav@166
    78
 * initialization is over, a <b>public static</b> method <em>initialized</em>
jaroslav@166
    79
 * in <code>YourMain</code> will be called with provided string parameters.
jaroslav@166
    80
 * <p>
jaroslav@166
    81
 * This module provides only API for building browsers. To use it properly one
jaroslav@166
    82
 * also needs an implementation on the classpath of one's application. For example
jaroslav@166
    83
 * use: <pre>
jaroslav@166
    84
 * &lt;dependency&gt;
jaroslav@361
    85
 *   &lt;groupId&gt;org.netbeans.html&lt;/groupId&gt;
jaroslav@361
    86
 *   &lt;artifactId&gt;net.java.html.boot.fx&lt;/artifactId&gt;
jaroslav@166
    87
 *   &lt;scope&gt;runtime&lt;/scope&gt;
jaroslav@166
    88
 * &lt;/dependency&gt;
jaroslav@166
    89
 * </pre>
jaroslav@123
    90
 *
jaroslav@123
    91
 * @author Jaroslav Tulach <jtulach@netbeans.org>
jaroslav@123
    92
 */
jaroslav@123
    93
public final class BrowserBuilder {
jaroslav@156
    94
    private static final Logger LOG = Logger.getLogger(BrowserBuilder.class.getName());
jaroslav@156
    95
    
jaroslav@127
    96
    private String resource;
jaroslav@127
    97
    private Class<?> clazz;
jaroslav@146
    98
    private Class[] browserClass;
jaroslav@146
    99
    private Runnable onLoad;
jaroslav@156
   100
    private String methodName;
jaroslav@156
   101
    private String[] methodArgs;
jaroslav@288
   102
    private final Object[] context;
jaroslav@156
   103
    
jaroslav@288
   104
    private BrowserBuilder(Object[] context) {
jaroslav@288
   105
        this.context = context;
jaroslav@123
   106
    }
jaroslav@166
   107
jaroslav@166
   108
    /** Entry method to obtain a new browser builder. Follow by calling 
jaroslav@166
   109
     * its instance methods like {@link #loadClass(java.lang.Class)} and
jaroslav@166
   110
     * {@link #loadPage(java.lang.String)}.
jaroslav@166
   111
     * 
jaroslav@288
   112
     * @param context any instances that should be available to the builder -
jaroslav@288
   113
     *   implemenation dependant
jaroslav@166
   114
     * @return new browser builder
jaroslav@166
   115
     */
jaroslav@288
   116
    public static BrowserBuilder newBrowser(Object... context) {
jaroslav@288
   117
        return new BrowserBuilder(context);
jaroslav@123
   118
    }
jaroslav@123
   119
    
jaroslav@166
   120
    /** The class to load when the browser is initialized. This class
jaroslav@166
   121
     * is loaded by a special classloader (that supports {@link JavaScriptBody}
jaroslav@166
   122
     * and co.). 
jaroslav@166
   123
     * 
jaroslav@166
   124
     * @param mainClass the class to load and resolve when the browser is ready
jaroslav@166
   125
     * @return this builder
jaroslav@166
   126
     */
jaroslav@127
   127
    public BrowserBuilder loadClass(Class<?> mainClass) {
jaroslav@127
   128
        this.clazz = mainClass;
jaroslav@123
   129
        return this;
jaroslav@123
   130
    }
jaroslav@156
   131
jaroslav@166
   132
    /** Page to load into the browser. If the <code>page</code> represents
jaroslav@166
   133
     * a {@link URL} known to the Java system, the URL is passed to the browser. 
jaroslav@202
   134
     * If system property <code>browser.rootdir</code> is specified, then a
jaroslav@202
   135
     * file <code>page</code> relative to this directory is used as the URL.
jaroslav@202
   136
     * If no such file exists, the system seeks for the 
jaroslav@202
   137
     * resource via {@link Class#getResource(java.lang.String)}
jaroslav@202
   138
     * method (relative to the {@link #loadClass(java.lang.Class) specified class}). 
jaroslav@202
   139
     * If such resource is not found, a file relative to the location JAR
jaroslav@202
   140
     * that contains the {@link #loadClass(java.lang.Class) main class} is 
jaroslav@202
   141
     * searched for.
jaroslav@166
   142
     * 
jaroslav@166
   143
     * @param page the location (relative, absolute, or URL) of a page to load
jaroslav@166
   144
     * @return this browser
jaroslav@166
   145
     */
jaroslav@166
   146
    public BrowserBuilder loadPage(String page) {
jaroslav@166
   147
        this.resource = page;
jaroslav@166
   148
        return this;
jaroslav@166
   149
    }
jaroslav@166
   150
    
jaroslav@166
   151
    /** Specifies callback method to notify the application that the browser is ready.
jaroslav@166
   152
     * There should be a <b>public static</b> method in the class specified
jaroslav@166
   153
     * by {@link #loadClass(java.lang.Class)} which takes an array of {@link String}
jaroslav@166
   154
     * argument. The method is called on the browser dispatch thread one
jaroslav@166
   155
     * the browser finishes loading of the {@link #loadPage(java.lang.String) HTML page}.
jaroslav@166
   156
     * 
jaroslav@166
   157
     * @param methodName name of a method to seek for
jaroslav@166
   158
     * @param args parameters to pass to the method
jaroslav@166
   159
     * @return this builder
jaroslav@166
   160
     */
jaroslav@156
   161
    public BrowserBuilder invoke(String methodName, String... args) {
jaroslav@156
   162
        this.methodName = methodName;
jaroslav@156
   163
        this.methodArgs = args;
jaroslav@156
   164
        return this;
jaroslav@156
   165
    }
jaroslav@166
   166
jaroslav@166
   167
    /** Shows the browser, loads specified page in and executes the 
jaroslav@166
   168
     * {@link #invoke(java.lang.String, java.lang.String[]) initialization method}.
jaroslav@166
   169
     * The method returns when the browser is closed.
jaroslav@166
   170
     * 
jaroslav@166
   171
     * @throws NullPointerException if some of essential parameters (like {@link #loadPage(java.lang.String) page} or
jaroslav@166
   172
     *    {@link #loadClass(java.lang.Class) class} have not been specified
jaroslav@166
   173
     */
jaroslav@123
   174
    public void showAndWait() {
jaroslav@146
   175
        if (resource == null) {
jaroslav@166
   176
            throw new NullPointerException("Need to specify resource via loadPage method");
jaroslav@146
   177
        }
jaroslav@146
   178
        
jaroslav@148
   179
        URL url = null;
jaroslav@148
   180
        MalformedURLException mal = null;
jaroslav@148
   181
        try {
jaroslav@202
   182
            String baseURL = System.getProperty("browser.rootdir");
jaroslav@202
   183
            if (baseURL != null) {
jaroslav@202
   184
                url = new File(baseURL, resource).toURI().toURL();
jaroslav@202
   185
            } else {
jaroslav@202
   186
                url = new URL(resource);
jaroslav@202
   187
            }
jaroslav@148
   188
        } catch (MalformedURLException ex) {
jaroslav@148
   189
            mal = ex;
jaroslav@148
   190
        }
jaroslav@128
   191
        if (url == null) {
jaroslav@148
   192
            url = clazz.getResource(resource);
jaroslav@148
   193
        }
jaroslav@148
   194
        if (url == null) {
jaroslav@202
   195
            URL jar = clazz.getProtectionDomain().getCodeSource().getLocation();
jaroslav@202
   196
            try {
jaroslav@202
   197
                url = new URL(jar, resource);
jaroslav@202
   198
            } catch (MalformedURLException ex) {
jaroslav@202
   199
                ex.initCause(mal);
jaroslav@202
   200
                mal = ex;
jaroslav@202
   201
            }
jaroslav@202
   202
        }
jaroslav@202
   203
        if (url == null) {
jaroslav@148
   204
            IllegalStateException ise = new IllegalStateException("Can't find resouce: " + resource + " relative to " + clazz);
jaroslav@148
   205
            if (mal != null) {
jaroslav@148
   206
                ise.initCause(mal);
jaroslav@148
   207
            }
jaroslav@148
   208
            throw ise;
jaroslav@128
   209
        }
jaroslav@288
   210
        
jaroslav@288
   211
        Fn.Presenter dfnr = null;
jaroslav@288
   212
        for (Object o : context) {
jaroslav@288
   213
            if (o instanceof Fn.Presenter) {
jaroslav@288
   214
                dfnr = (Fn.Presenter)o;
jaroslav@288
   215
                break;
jaroslav@288
   216
            }
jaroslav@288
   217
        }
jaroslav@127
   218
jaroslav@288
   219
        if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) {
jaroslav@288
   220
            dfnr = o;
jaroslav@288
   221
            break;
jaroslav@288
   222
        }
jaroslav@288
   223
        
jaroslav@288
   224
        if (dfnr == null) {
jaroslav@288
   225
            throw new IllegalStateException("Can't find any Fn.Presenter");
jaroslav@288
   226
        }
jaroslav@288
   227
        
jaroslav@288
   228
        final ClassLoader loader;
jaroslav@288
   229
        if (FnUtils.isJavaScriptCapable(clazz.getClassLoader())) {
jaroslav@288
   230
            loader = clazz.getClassLoader();
jaroslav@288
   231
        } else {
jaroslav@288
   232
            FImpl impl = new FImpl(clazz.getClassLoader());
jaroslav@288
   233
            loader = FnUtils.newLoader(impl, dfnr, clazz.getClassLoader().getParent());
jaroslav@288
   234
        }
jaroslav@128
   235
jaroslav@288
   236
        final Fn.Presenter currentP = dfnr;
jaroslav@288
   237
        class OnPageLoad implements Runnable {
jaroslav@288
   238
            @Override
jaroslav@288
   239
            public void run() {
jaroslav@288
   240
                try {
jaroslav@288
   241
                    Thread.currentThread().setContextClassLoader(loader);
jaroslav@288
   242
                    Class<?> newClazz = Class.forName(clazz.getName(), true, loader);
jaroslav@288
   243
                    if (browserClass != null) {
jaroslav@288
   244
                        browserClass[0] = newClazz;
jaroslav@288
   245
                    }
jaroslav@288
   246
                    if (onLoad != null) {
jaroslav@288
   247
                        onLoad.run();
jaroslav@288
   248
                    }
jaroslav@288
   249
                    INIT: if (methodName != null) {
jaroslav@288
   250
                        Throwable firstError = null;
jaroslav@288
   251
                        if (methodArgs.length == 0) {
jaroslav@156
   252
                            try {
jaroslav@288
   253
                                Method m = newClazz.getMethod(methodName);
jaroslav@309
   254
                                FnContext.currentPresenter(currentP);
jaroslav@288
   255
                                m.invoke(null);
jaroslav@288
   256
                                break INIT;
jaroslav@277
   257
                            } catch (Throwable ex) {
jaroslav@288
   258
                                firstError = ex;
jaroslav@288
   259
                            } finally {
jaroslav@309
   260
                                FnContext.currentPresenter(null);
jaroslav@156
   261
                            }
jaroslav@156
   262
                        }
jaroslav@288
   263
                        try {
jaroslav@288
   264
                            Method m = newClazz.getMethod(methodName, String[].class);
jaroslav@309
   265
                            FnContext.currentPresenter(currentP);
jaroslav@288
   266
                            m.invoke(m, (Object) methodArgs);
jaroslav@288
   267
                        } catch (Throwable ex) {
jaroslav@288
   268
                            if (firstError != null) {
jaroslav@288
   269
                                LOG.log(Level.SEVERE, "Can't call " + methodName, firstError);
jaroslav@288
   270
                            }
jaroslav@288
   271
                            LOG.log(Level.SEVERE, "Can't call " + methodName + " with args " + Arrays.toString(methodArgs), ex);
jaroslav@288
   272
                        } finally {
jaroslav@309
   273
                            FnContext.currentPresenter(null);
jaroslav@288
   274
                        }
jaroslav@128
   275
                    }
jaroslav@288
   276
                } catch (ClassNotFoundException ex) {
jaroslav@288
   277
                    LOG.log(Level.SEVERE, "Can't load " + clazz.getName(), ex);
jaroslav@128
   278
                }
jaroslav@127
   279
            }
jaroslav@127
   280
        }
jaroslav@288
   281
        dfnr.displayPage(url, new OnPageLoad());
jaroslav@288
   282
        return;
jaroslav@127
   283
    }
jaroslav@146
   284
jaroslav@128
   285
    private static final class FImpl implements FindResources {
jaroslav@128
   286
        final ClassLoader l;
jaroslav@127
   287
jaroslav@127
   288
        public FImpl(ClassLoader l) {
jaroslav@127
   289
            this.l = l;
jaroslav@127
   290
        }
jaroslav@127
   291
jaroslav@127
   292
        @Override
jaroslav@127
   293
        public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
jaroslav@127
   294
            if (oneIsEnough) {
jaroslav@127
   295
                URL u = l.getResource(path);
jaroslav@127
   296
                if (u != null) {
jaroslav@127
   297
                    results.add(u);
jaroslav@127
   298
                }
jaroslav@127
   299
            } else {
jaroslav@127
   300
                try {
jaroslav@127
   301
                    Enumeration<URL> en = l.getResources(path);
jaroslav@127
   302
                    while (en.hasMoreElements()) {
jaroslav@127
   303
                        results.add(en.nextElement());
jaroslav@127
   304
                    }
jaroslav@127
   305
                } catch (IOException ex) {
jaroslav@127
   306
                    // no results
jaroslav@127
   307
                }
jaroslav@127
   308
            }
jaroslav@127
   309
        }
jaroslav@127
   310
        
jaroslav@123
   311
    }
jaroslav@123
   312
}