2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
9 * The contents of this file are subject to the terms of either the GNU
10 * General Public License Version 2 only ("GPL") or the Common
11 * Development and Distribution License("CDDL") (collectively, the
12 * "License"). You may not use this file except in compliance with the
13 * License. You can obtain a copy of the License at
14 * http://www.netbeans.org/cddl-gplv2.html
15 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16 * specific language governing permissions and limitations under the
17 * License. When distributing the software, include this License Header
18 * Notice in each file and include the License file at
19 * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
20 * particular file as subject to the "Classpath" exception as provided
21 * by Oracle in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the
23 * License Header, with the fields enclosed by brackets [] replaced by
24 * your own identifying information:
25 * "Portions Copyrighted [year] [name of copyright owner]"
29 * The Original Software is NetBeans. The Initial Developer of the Original
30 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
32 * If you wish your version of this file to be governed by only the CDDL
33 * or only the GPL Version 2, indicate your decision by adding
34 * "[Contributor] elects to include this software in this distribution
35 * under the [CDDL or GPL Version 2] license." If you do not indicate a
36 * single choice of license, a recipient has the option to distribute
37 * your version of this file under either the CDDL, the GPL Version 2 or
38 * to extend the choice of license to its licensees as provided above.
39 * However, if you add GPL Version 2 code and therefore, elected the GPL
40 * Version 2 license, then the option applies only if the new code is
41 * made subject to such option by the copyright holder.
43 package net.java.html.boot;
46 import java.io.IOException;
47 import java.lang.reflect.Method;
48 import java.net.MalformedURLException;
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;
62 /** Use this builder to launch your Java/HTML based application. Typical
63 * usage in a main method of your application looks like this:
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()};
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.
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
85 * <groupId>org.netbeans.html</groupId>
86 * <artifactId>net.java.html.boot.fx</artifactId>
87 * <scope>runtime</scope>
91 * @author Jaroslav Tulach <jtulach@netbeans.org>
93 public final class BrowserBuilder {
94 private static final Logger LOG = Logger.getLogger(BrowserBuilder.class.getName());
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;
104 private BrowserBuilder(Object[] context) {
105 this.context = context;
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)}.
112 * @param context any instances that should be available to the builder -
113 * implementation dependant
114 * @return new browser builder
116 public static BrowserBuilder newBrowser(Object... context) {
117 return new BrowserBuilder(context);
120 /** The class to load when the browser is initialized. This class
121 * is loaded by a special classloader (that supports {@link JavaScriptBody}
124 * @param mainClass the class to load and resolve when the browser is ready
125 * @return this builder
127 public BrowserBuilder loadClass(Class<?> mainClass) {
128 this.clazz = mainClass;
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
143 * @param page the location (relative, absolute, or URL) of a page to load
144 * @return this browser
146 public BrowserBuilder loadPage(String page) {
147 this.resource = page;
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}.
157 * @param methodName name of a method to seek for
158 * @param args parameters to pass to the method
159 * @return this builder
161 public BrowserBuilder invoke(String methodName, String... args) {
162 this.methodName = methodName;
163 this.methodArgs = args;
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.
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
174 public void showAndWait() {
175 if (resource == null) {
176 throw new NullPointerException("Need to specify resource via loadPage method");
180 MalformedURLException mal = null;
182 String baseURL = System.getProperty("browser.rootdir");
183 if (baseURL != null) {
184 url = new File(baseURL, resource).toURI().toURL();
186 url = new URL(resource);
188 } catch (MalformedURLException ex) {
192 url = clazz.getResource(resource);
195 URL jar = clazz.getProtectionDomain().getCodeSource().getLocation();
197 url = new URL(jar, resource);
198 } catch (MalformedURLException ex) {
204 IllegalStateException ise = new IllegalStateException("Can't find resouce: " + resource + " relative to " + clazz);
211 Fn.Presenter dfnr = null;
212 for (Object o : context) {
213 if (o instanceof Fn.Presenter) {
214 dfnr = (Fn.Presenter)o;
219 if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) {
225 throw new IllegalStateException("Can't find any Fn.Presenter");
228 final ClassLoader loader;
229 if (FnUtils.isJavaScriptCapable(clazz.getClassLoader())) {
230 loader = clazz.getClassLoader();
232 FImpl impl = new FImpl(clazz.getClassLoader());
233 loader = FnUtils.newLoader(impl, dfnr, clazz.getClassLoader().getParent());
236 final Fn.Presenter currentP = dfnr;
237 class OnPageLoad implements Runnable {
241 Thread.currentThread().setContextClassLoader(loader);
242 Class<?> newClazz = Class.forName(clazz.getName(), true, loader);
243 if (browserClass != null) {
244 browserClass[0] = newClazz;
246 if (onLoad != null) {
249 INIT: if (methodName != null) {
250 Throwable firstError = null;
251 if (methodArgs.length == 0) {
253 Method m = newClazz.getMethod(methodName);
254 FnContext.currentPresenter(currentP);
257 } catch (Throwable ex) {
260 FnContext.currentPresenter(null);
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);
271 LOG.log(Level.SEVERE, "Can't call " + methodName + " with args " + Arrays.toString(methodArgs), ex);
273 FnContext.currentPresenter(null);
276 } catch (ClassNotFoundException ex) {
277 LOG.log(Level.SEVERE, "Can't load " + clazz.getName(), ex);
281 dfnr.displayPage(url, new OnPageLoad());
285 private static final class FImpl implements FindResources {
288 public FImpl(ClassLoader l) {
293 public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
295 URL u = l.getResource(path);
301 Enumeration<URL> en = l.getResources(path);
302 while (en.hasMoreElements()) {
303 results.add(en.nextElement());
305 } catch (IOException ex) {