boot/src/main/java/net/java/html/boot/BrowserBuilder.java
author Jaroslav Tulach <jaroslav.tulach@netbeans.org>
Fri, 07 Feb 2014 07:44:34 +0100
changeset 551 7ca2253fa86d
parent 509 186de7f12a7c
child 569 245637e6d8db
permissions -rw-r--r--
Updating copyright headers to mention current year
     1 /**
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     5  *
     6  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
     7  * Other names may be trademarks of their respective owners.
     8  *
     9  * The contents of this file are subject to the terms of either the GNU
    10  * General Public License Version 2 only ("GPL") or the Common
    11  * Development and Distribution License("CDDL") (collectively, the
    12  * "License"). You may not use this file except in compliance with the
    13  * License. You can obtain a copy of the License at
    14  * http://www.netbeans.org/cddl-gplv2.html
    15  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    16  * specific language governing permissions and limitations under the
    17  * License.  When distributing the software, include this License Header
    18  * Notice in each file and include the License file at
    19  * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    20  * particular file as subject to the "Classpath" exception as provided
    21  * by Oracle in the GPL Version 2 section of the License file that
    22  * accompanied this code. If applicable, add the following below the
    23  * License Header, with the fields enclosed by brackets [] replaced by
    24  * your own identifying information:
    25  * "Portions Copyrighted [year] [name of copyright owner]"
    26  *
    27  * Contributor(s):
    28  *
    29  * The Original Software is NetBeans. The Initial Developer of the Original
    30  * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    31  *
    32  * If you wish your version of this file to be governed by only the CDDL
    33  * or only the GPL Version 2, indicate your decision by adding
    34  * "[Contributor] elects to include this software in this distribution
    35  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    36  * single choice of license, a recipient has the option to distribute
    37  * your version of this file under either the CDDL, the GPL Version 2 or
    38  * to extend the choice of license to its licensees as provided above.
    39  * However, if you add GPL Version 2 code and therefore, elected the GPL
    40  * Version 2 license, then the option applies only if the new code is
    41  * made subject to such option by the copyright holder.
    42  */
    43 package net.java.html.boot;
    44 
    45 import java.io.File;
    46 import java.io.IOException;
    47 import java.lang.reflect.Method;
    48 import java.net.MalformedURLException;
    49 import java.net.URL;
    50 import java.util.Arrays;
    51 import java.util.Collection;
    52 import java.util.Enumeration;
    53 import java.util.ServiceLoader;
    54 import java.util.logging.Level;
    55 import java.util.logging.Logger;
    56 import net.java.html.js.JavaScriptBody;
    57 import org.apidesign.html.boot.spi.Fn;
    58 import org.netbeans.html.boot.impl.FindResources;
    59 import org.netbeans.html.boot.impl.FnContext;
    60 import org.netbeans.html.boot.impl.FnUtils;
    61 
    62 /** Use this builder to launch your Java/HTML based application. Typical
    63  * usage in a main method of your application looks like this: 
    64  * <pre>
    65  * 
    66  * <b>public static void</b> <em>main</em>(String... args) {
    67  *     BrowserBuilder.{@link #newBrowser}.
    68  *          {@link #loadClass(java.lang.Class) loadClass(YourMain.class)}.
    69  *          {@link #loadPage(java.lang.String) loadPage("index.html")}.
    70  *          {@link #invoke(java.lang.String, java.lang.String[]) invoke("initialized", args)}.
    71  *          {@link #showAndWait()};
    72  *     System.exit(0);
    73  * }
    74  * </pre>
    75  * The above will load <code>YourMain</code> class via
    76  * a special classloader, it will locate an <code>index.html</code> (relative
    77  * to <code>YourMain</code> class) and show it in a browser window. When the
    78  * initialization is over, a <b>public static</b> method <em>initialized</em>
    79  * in <code>YourMain</code> will be called with provided string parameters.
    80  * <p>
    81  * This module provides only API for building browsers. To use it properly one
    82  * also needs an implementation on the classpath of one's application. For example
    83  * use: <pre>
    84  * &lt;dependency&gt;
    85  *   &lt;groupId&gt;org.netbeans.html&lt;/groupId&gt;
    86  *   &lt;artifactId&gt;net.java.html.boot.fx&lt;/artifactId&gt;
    87  *   &lt;scope&gt;runtime&lt;/scope&gt;
    88  * &lt;/dependency&gt;
    89  * </pre>
    90  *
    91  * @author Jaroslav Tulach <jtulach@netbeans.org>
    92  */
    93 public final class BrowserBuilder {
    94     private static final Logger LOG = Logger.getLogger(BrowserBuilder.class.getName());
    95     
    96     private String resource;
    97     private Class<?> clazz;
    98     private Class[] browserClass;
    99     private Runnable onLoad;
   100     private String methodName;
   101     private String[] methodArgs;
   102     private final Object[] context;
   103     
   104     private BrowserBuilder(Object[] context) {
   105         this.context = context;
   106     }
   107 
   108     /** Entry method to obtain a new browser builder. Follow by calling 
   109      * its instance methods like {@link #loadClass(java.lang.Class)} and
   110      * {@link #loadPage(java.lang.String)}.
   111      * 
   112      * @param context any instances that should be available to the builder -
   113      *   implementation dependant
   114      * @return new browser builder
   115      */
   116     public static BrowserBuilder newBrowser(Object... context) {
   117         return new BrowserBuilder(context);
   118     }
   119     
   120     /** The class to load when the browser is initialized. This class
   121      * is loaded by a special classloader (that supports {@link JavaScriptBody}
   122      * and co.). 
   123      * 
   124      * @param mainClass the class to load and resolve when the browser is ready
   125      * @return this builder
   126      */
   127     public BrowserBuilder loadClass(Class<?> mainClass) {
   128         this.clazz = mainClass;
   129         return this;
   130     }
   131 
   132     /** Page to load into the browser. If the <code>page</code> represents
   133      * a {@link URL} known to the Java system, the URL is passed to the browser. 
   134      * If system property <code>browser.rootdir</code> is specified, then a
   135      * file <code>page</code> relative to this directory is used as the URL.
   136      * If no such file exists, the system seeks for the 
   137      * resource via {@link Class#getResource(java.lang.String)}
   138      * method (relative to the {@link #loadClass(java.lang.Class) specified class}). 
   139      * If such resource is not found, a file relative to the location JAR
   140      * that contains the {@link #loadClass(java.lang.Class) main class} is 
   141      * searched for.
   142      * 
   143      * @param page the location (relative, absolute, or URL) of a page to load
   144      * @return this browser
   145      */
   146     public BrowserBuilder loadPage(String page) {
   147         this.resource = page;
   148         return this;
   149     }
   150     
   151     /** Specifies callback method to notify the application that the browser is ready.
   152      * There should be a <b>public static</b> method in the class specified
   153      * by {@link #loadClass(java.lang.Class)} which takes an array of {@link String}
   154      * argument. The method is called on the browser dispatch thread one
   155      * the browser finishes loading of the {@link #loadPage(java.lang.String) HTML page}.
   156      * 
   157      * @param methodName name of a method to seek for
   158      * @param args parameters to pass to the method
   159      * @return this builder
   160      */
   161     public BrowserBuilder invoke(String methodName, String... args) {
   162         this.methodName = methodName;
   163         this.methodArgs = args;
   164         return this;
   165     }
   166 
   167     /** Shows the browser, loads specified page in and executes the 
   168      * {@link #invoke(java.lang.String, java.lang.String[]) initialization method}.
   169      * The method returns when the browser is closed.
   170      * 
   171      * @throws NullPointerException if some of essential parameters (like {@link #loadPage(java.lang.String) page} or
   172      *    {@link #loadClass(java.lang.Class) class} have not been specified
   173      */
   174     public void showAndWait() {
   175         if (resource == null) {
   176             throw new NullPointerException("Need to specify resource via loadPage method");
   177         }
   178         
   179         URL url = null;
   180         MalformedURLException mal = null;
   181         try {
   182             String baseURL = System.getProperty("browser.rootdir");
   183             if (baseURL != null) {
   184                 url = new File(baseURL, resource).toURI().toURL();
   185             } else {
   186                 url = new URL(resource);
   187             }
   188         } catch (MalformedURLException ex) {
   189             mal = ex;
   190         }
   191         if (url == null) {
   192             url = clazz.getResource(resource);
   193         }
   194         if (url == null) {
   195             URL jar = clazz.getProtectionDomain().getCodeSource().getLocation();
   196             try {
   197                 url = new URL(jar, resource);
   198             } catch (MalformedURLException ex) {
   199                 ex.initCause(mal);
   200                 mal = ex;
   201             }
   202         }
   203         if (url == null) {
   204             IllegalStateException ise = new IllegalStateException("Can't find resouce: " + resource + " relative to " + clazz);
   205             if (mal != null) {
   206                 ise.initCause(mal);
   207             }
   208             throw ise;
   209         }
   210         
   211         Fn.Presenter dfnr = null;
   212         for (Object o : context) {
   213             if (o instanceof Fn.Presenter) {
   214                 dfnr = (Fn.Presenter)o;
   215                 break;
   216             }
   217         }
   218 
   219         if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) {
   220             dfnr = o;
   221             break;
   222         }
   223         
   224         if (dfnr == null) {
   225             throw new IllegalStateException("Can't find any Fn.Presenter");
   226         }
   227         
   228         final ClassLoader loader;
   229         if (FnUtils.isJavaScriptCapable(clazz.getClassLoader())) {
   230             loader = clazz.getClassLoader();
   231         } else {
   232             FImpl impl = new FImpl(clazz.getClassLoader());
   233             loader = FnUtils.newLoader(impl, dfnr, clazz.getClassLoader().getParent());
   234         }
   235 
   236         final Fn.Presenter currentP = dfnr;
   237         class OnPageLoad implements Runnable {
   238             @Override
   239             public void run() {
   240                 try {
   241                     Thread.currentThread().setContextClassLoader(loader);
   242                     Class<?> newClazz = Class.forName(clazz.getName(), true, loader);
   243                     if (browserClass != null) {
   244                         browserClass[0] = newClazz;
   245                     }
   246                     if (onLoad != null) {
   247                         onLoad.run();
   248                     }
   249                     INIT: if (methodName != null) {
   250                         Throwable firstError = null;
   251                         if (methodArgs.length == 0) {
   252                             try {
   253                                 Method m = newClazz.getMethod(methodName);
   254                                 FnContext.currentPresenter(currentP);
   255                                 m.invoke(null);
   256                                 break INIT;
   257                             } catch (Throwable ex) {
   258                                 firstError = ex;
   259                             } finally {
   260                                 FnContext.currentPresenter(null);
   261                             }
   262                         }
   263                         try {
   264                             Method m = newClazz.getMethod(methodName, String[].class);
   265                             FnContext.currentPresenter(currentP);
   266                             m.invoke(m, (Object) methodArgs);
   267                         } catch (Throwable ex) {
   268                             if (firstError != null) {
   269                                 LOG.log(Level.SEVERE, "Can't call " + methodName, firstError);
   270                             }
   271                             LOG.log(Level.SEVERE, "Can't call " + methodName + " with args " + Arrays.toString(methodArgs), ex);
   272                         } finally {
   273                             FnContext.currentPresenter(null);
   274                         }
   275                     }
   276                 } catch (ClassNotFoundException ex) {
   277                     LOG.log(Level.SEVERE, "Can't load " + clazz.getName(), ex);
   278                 }
   279             }
   280         }
   281         dfnr.displayPage(url, new OnPageLoad());
   282         return;
   283     }
   284 
   285     private static final class FImpl implements FindResources {
   286         final ClassLoader l;
   287 
   288         public FImpl(ClassLoader l) {
   289             this.l = l;
   290         }
   291 
   292         @Override
   293         public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
   294             if (oneIsEnough) {
   295                 URL u = l.getResource(path);
   296                 if (u != null) {
   297                     results.add(u);
   298                 }
   299             } else {
   300                 try {
   301                     Enumeration<URL> en = l.getResources(path);
   302                     while (en.hasMoreElements()) {
   303                         results.add(en.nextElement());
   304                     }
   305                 } catch (IOException ex) {
   306                     // no results
   307                 }
   308             }
   309         }
   310         
   311     }
   312 }