Allow use of lamda functions in onLoad callback. Lambda function classes cannot be loaded by name, so avoid doing so when onLoad is specified.
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.io.InputStream;
48 import java.lang.reflect.Method;
49 import java.net.HttpURLConnection;
50 import java.net.JarURLConnection;
51 import java.net.MalformedURLException;
53 import java.net.URLConnection;
54 import java.security.ProtectionDomain;
55 import java.util.Arrays;
56 import java.util.Collection;
57 import java.util.Enumeration;
58 import java.util.Locale;
59 import java.util.ServiceLoader;
60 import java.util.concurrent.Executor;
61 import java.util.logging.Level;
62 import java.util.logging.Logger;
63 import net.java.html.BrwsrCtx;
64 import net.java.html.js.JavaScriptBody;
65 import org.netbeans.html.boot.spi.Fn;
66 import org.netbeans.html.boot.spi.Fn.Presenter;
67 import org.netbeans.html.context.spi.Contexts;
68 import org.netbeans.html.context.spi.Contexts.Id;
69 import org.netbeans.html.boot.impl.FindResources;
70 import org.netbeans.html.boot.impl.FnContext;
72 /** Use this builder to launch your Java/HTML based application. Typical
73 * usage in a main method of your application looks like this:
76 * <b>public static void</b> <em>main</em>(String... args) {
77 * BrowserBuilder.{@link #newBrowser newBrowser()}.
78 * {@link #loadClass(java.lang.Class) loadClass(YourMain.class)}.
79 * {@link #loadPage(java.lang.String) loadPage("index.html")}.
80 * {@link #locale(java.util.Locale) locale}({@link Locale#getDefault()}).
81 * {@link #invoke(java.lang.String, java.lang.String[]) invoke("initialized", args)}.
82 * {@link #showAndWait()};
86 * The above will load <code>YourMain</code> class via
87 * a special classloader, it will locate an <code>index.html</code> (relative
88 * to <code>YourMain</code> class) and show it in a browser window. When the
89 * initialization is over, a <b>public static</b> method <em>initialized</em>
90 * in <code>YourMain</code> will be called with provided string parameters.
92 * This module provides only API for building browsers. To use it properly one
93 * also needs an implementation on the classpath of one's application. For example
96 * <groupId>org.netbeans.html</groupId>
97 * <artifactId>net.java.html.boot.fx</artifactId>
98 * <scope>runtime</scope>
102 * @author Jaroslav Tulach
104 public final class BrowserBuilder {
105 private static final Logger LOG = Logger.getLogger(BrowserBuilder.class.getName());
107 private String resource;
108 private Class<?> clazz;
109 private Runnable onLoad;
110 private String methodName;
111 private String[] methodArgs;
112 private final Object[] context;
113 private ClassLoader loader;
114 private Locale locale;
116 private BrowserBuilder(Object[] context) {
117 this.context = context;
120 /** Entry method to obtain a new browser builder. Follow by calling
121 * its instance methods like {@link #loadClass(java.lang.Class)} and
122 * {@link #loadPage(java.lang.String)}.
123 * Since introduction of {@link Id technology identifiers} the
124 * provided <code>context</code> objects are also passed to the
125 * {@link BrwsrCtx context} when it is being
126 * {@link Contexts#newBuilder(java.lang.Object...) created}
127 * and can influence the selection
128 * of available technologies
129 * (like {@link org.netbeans.html.json.spi.Technology},
130 * {@link org.netbeans.html.json.spi.Transfer} or
131 * {@link org.netbeans.html.json.spi.WSTransfer}) by name.
133 * @param context any instances that should be available to the builder -
134 * implementation dependant
135 * @return new browser builder
137 public static BrowserBuilder newBrowser(Object... context) {
138 return new BrowserBuilder(context);
141 /** The class to load when the browser is initialized. This class
142 * is loaded by a special classloader (that supports {@link JavaScriptBody}
145 * @param mainClass the class to load and resolve when the browser is ready
146 * @return this builder
148 public BrowserBuilder loadClass(Class<?> mainClass) {
149 this.clazz = mainClass;
153 /** Allows one to specify a runnable that should be invoked when a load
154 * of a page is finished. This method may be used in addition or instead
155 * of {@link #loadClass(java.lang.Class)} and
156 * {@link #invoke(java.lang.String, java.lang.String...)} methods.
158 * @param r the code to run when the page is loaded
159 * @return this builder
162 public BrowserBuilder loadFinished(Runnable r) {
167 /** Page to load into the browser. If the <code>page</code> represents
168 * a {@link URL} known to the Java system, the URL is passed to the browser.
169 * If system property <code>browser.rootdir</code> is specified, then a
170 * file <code>page</code> relative to this directory is used as the URL.
171 * If no such file exists, the system seeks for the
172 * resource via {@link Class#getResource(java.lang.String)}
173 * method (relative to the {@link #loadClass(java.lang.Class) specified class}).
174 * If such resource is not found, a file relative to the location JAR
175 * that contains the {@link #loadClass(java.lang.Class) main class} is
178 * The search honors provided {@link #locale}, if specified.
179 * E.g. it will prefer <code>index_cs.html</code> over <code>index.html</code>
180 * if the locale is set to <code>cs_CZ</code>.
182 * @param page the location (relative, absolute, or URL) of a page to load
183 * @return this builder
185 public BrowserBuilder loadPage(String page) {
186 this.resource = page;
190 /** Locale to use when searching for an initial {@link #loadPage(java.lang.String) page to load}.
191 * Localization is best done by providing different versions of the
192 * initial page with appropriate suffixes (like <code>index_cs.html</code>).
193 * Then one can call this method with value of {@link Locale#getDefault()}
194 * to instruct the builder to use the user's current locale.
196 * @param locale the locale to use or <code>null</code> if no suffix search should be performed
197 * @return this builder
200 public BrowserBuilder locale(Locale locale) {
201 this.locale = locale;
205 /** Specifies callback method to notify the application that the browser is ready.
206 * There should be a <b>public static</b> method in the class specified
207 * by {@link #loadClass(java.lang.Class)} which takes an array of {@link String}
208 * argument. The method is called on the browser dispatch thread one
209 * the browser finishes loading of the {@link #loadPage(java.lang.String) HTML page}.
211 * @param methodName name of a method to seek for
212 * @param args parameters to pass to the method
213 * @return this builder
215 public BrowserBuilder invoke(String methodName, String... args) {
216 this.methodName = methodName;
217 this.methodArgs = args;
221 /** Loader to use when searching for classes to initialize.
222 * If specified, this loader is going to be used to load {@link Presenter}
223 * and {@link Contexts#fillInByProviders(java.lang.Class, org.netbeans.html.context.spi.Contexts.Builder) fill} {@link BrwsrCtx} in.
224 * Specifying special classloader may be useful in modular systems,
225 * like OSGi, where one needs to load classes from many otherwise independent
228 * @param l the loader to use (or <code>null</code>)
229 * @return this builder
232 public BrowserBuilder classloader(ClassLoader l) {
237 /** Shows the browser, loads specified page in and executes the
238 * {@link #invoke(java.lang.String, java.lang.String[]) initialization method}.
239 * The method returns when the browser is closed.
241 * @throws NullPointerException if some of essential parameters (like {@link #loadPage(java.lang.String) page} or
242 * {@link #loadClass(java.lang.Class) class} have not been specified
244 public void showAndWait() {
245 if (resource == null) {
246 throw new NullPointerException("Need to specify resource via loadPage method");
249 final Class<?> myCls;
252 } else if (onLoad != null) {
253 myCls = onLoad.getClass();
255 throw new NullPointerException("loadClass, neither loadFinished was called!");
257 IOException mal[] = { null };
258 URL url = findLocalizedResourceURL(resource, locale, mal, myCls);
260 Fn.Presenter dfnr = null;
261 for (Object o : context) {
262 if (o instanceof Fn.Presenter) {
263 dfnr = (Fn.Presenter)o;
268 if (dfnr == null && loader != null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class, loader)) {
273 if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) {
279 throw new IllegalStateException("Can't find any Fn.Presenter");
282 final ClassLoader activeLoader;
283 if (loader != null) {
284 final URL res = FnContext.isJavaScriptCapable(loader);
286 throw new IllegalStateException("Loader " + loader +
287 " cannot resolve @JavaScriptBody, because of " + res
290 activeLoader = loader;
292 final URL res = FnContext.isJavaScriptCapable(myCls.getClassLoader());
294 activeLoader = myCls.getClassLoader();
296 FImpl impl = new FImpl(myCls.getClassLoader());
297 activeLoader = FnContext.newLoader(res, impl, dfnr, myCls.getClassLoader().getParent());
298 if (activeLoader == null) {
299 throw new IllegalStateException("Cannot find asm-5.0.jar classes!");
304 final Fn.Presenter dP = dfnr;
306 class OnPageLoad implements Runnable {
310 final Fn.Presenter aP = Fn.activePresenter();
311 final Fn.Presenter currentP = aP != null ? aP : dP;
313 Thread.currentThread().setContextClassLoader(activeLoader);
314 final Class<?> newClazz = onLoad != null ?
316 Class.forName(myCls.getName(), true, activeLoader);
317 Contexts.Builder cb = Contexts.newBuilder(context);
318 if (!Contexts.fillInByProviders(newClazz, cb)) {
319 LOG.log(Level.WARNING, "Using empty technology for {0}", newClazz);
321 if (currentP instanceof Executor) {
322 cb.register(Executor.class, (Executor)currentP, 1000);
324 cb.register(Fn.Presenter.class, currentP, 1000);
325 BrwsrCtx c = cb.build();
327 class CallInitMethod implements Runnable {
330 Throwable firstError = null;
331 if (onLoad != null) {
333 FnContext.currentPresenter(currentP);
335 } catch (Throwable ex) {
338 FnContext.currentPresenter(null);
341 INIT: if (methodName != null) {
342 if (methodArgs.length == 0) {
344 Method m = newClazz.getMethod(methodName);
345 FnContext.currentPresenter(currentP);
349 } catch (Throwable ex) {
352 FnContext.currentPresenter(null);
356 Method m = newClazz.getMethod(methodName, String[].class);
357 FnContext.currentPresenter(currentP);
358 m.invoke(m, (Object) methodArgs);
360 } catch (Throwable ex) {
361 LOG.log(Level.SEVERE, "Can't call " + methodName + " with args " + Arrays.toString(methodArgs), ex);
363 FnContext.currentPresenter(null);
366 if (firstError != null) {
367 LOG.log(Level.SEVERE, "Can't initialize the view", firstError);
372 c.execute(new CallInitMethod());
373 } catch (ClassNotFoundException ex) {
374 LOG.log(Level.SEVERE, "Can't load " + myCls.getName(), ex);
378 dfnr.displayPage(url, new OnPageLoad());
381 private static URL findResourceURL(String resource, String suffix, IOException[] mal, Class<?> relativeTo) {
382 if (suffix != null) {
383 int lastDot = resource.lastIndexOf('.');
385 resource = resource.substring(0, lastDot) + suffix + resource.substring(lastDot);
387 resource = resource + suffix;
393 String baseURL = System.getProperty("browser.rootdir"); // NOI18N
394 if (baseURL != null) {
395 URL u = new File(baseURL, resource).toURI().toURL();
402 URL u = new URL(resource);
403 if (suffix == null || isReal(u)) {
408 } catch (MalformedURLException ex) {
413 url = relativeTo.getResource(resource);
416 final ProtectionDomain pd = relativeTo.getProtectionDomain();
417 if (pd != null && pd.getCodeSource() != null) {
418 URL jar = pd.getCodeSource().getLocation();
420 URL u = new URL(jar, resource);
424 } catch (MalformedURLException ex) {
425 ex.initCause(mal[0]);
431 URL res = BrowserBuilder.class.getResource("html4j.txt");
432 LOG.log(Level.FINE, "Found html4j {0}", res);
435 URLConnection c = res.openConnection();
436 LOG.log(Level.FINE, "testing : {0}", c);
437 if (c instanceof JarURLConnection) {
438 JarURLConnection jc = (JarURLConnection) c;
439 URL base = jc.getJarFileURL();
440 for (int i = 0; i < 50; i++) {
441 URL u = new URL(base, resource);
446 base = new URL(base, "..");
449 } catch (IOException ex) {
457 static URL findLocalizedResourceURL(String resource, Locale l, IOException[] mal, Class<?> relativeTo) {
460 url = findResourceURL(resource, "_" + l.getLanguage() + "_" + l.getCountry(), mal, relativeTo);
464 url = findResourceURL(resource, "_" + l.getLanguage(), mal, relativeTo);
469 return findResourceURL(resource, null, mal, relativeTo);
472 private static boolean isReal(URL u) {
474 URLConnection conn = u.openConnection();
475 if (conn instanceof HttpURLConnection) {
476 HttpURLConnection hc = (HttpURLConnection) conn;
477 hc.setReadTimeout(5000);
478 if (hc.getResponseCode() >= 300) {
479 throw new IOException("Wrong code: " + hc.getResponseCode());
482 InputStream is = conn.getInputStream();
484 LOG.log(Level.FINE, "found real url: {0}", u);
486 } catch (IOException ignore) {
487 LOG.log(Level.FINE, "Cannot open " + u, ignore);
492 private static final class FImpl implements FindResources {
495 public FImpl(ClassLoader l) {
500 public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
502 URL u = l.getResource(path);
508 Enumeration<URL> en = l.getResources(path);
509 while (en.hasMoreElements()) {
510 results.add(en.nextElement());
512 } catch (IOException ex) {