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.
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.context.spi.Contexts;
67 import org.netbeans.html.boot.impl.FindResources;
68 import org.netbeans.html.boot.impl.FnContext;
69 import org.netbeans.html.boot.impl.FnUtils;
71 /** Use this builder to launch your Java/HTML based application. Typical
72 * usage in a main method of your application looks like this:
75 * <b>public static void</b> <em>main</em>(String... args) {
76 * BrowserBuilder.{@link #newBrowser newBrowser()}.
77 * {@link #loadClass(java.lang.Class) loadClass(YourMain.class)}.
78 * {@link #loadPage(java.lang.String) loadPage("index.html")}.
79 * {@link #locale(java.util.Locale) locale}({@link Locale#getDefault()}).
80 * {@link #invoke(java.lang.String, java.lang.String[]) invoke("initialized", args)}.
81 * {@link #showAndWait()};
85 * The above will load <code>YourMain</code> class via
86 * a special classloader, it will locate an <code>index.html</code> (relative
87 * to <code>YourMain</code> class) and show it in a browser window. When the
88 * initialization is over, a <b>public static</b> method <em>initialized</em>
89 * in <code>YourMain</code> will be called with provided string parameters.
91 * This module provides only API for building browsers. To use it properly one
92 * also needs an implementation on the classpath of one's application. For example
95 * <groupId>org.netbeans.html</groupId>
96 * <artifactId>net.java.html.boot.fx</artifactId>
97 * <scope>runtime</scope>
101 * @author Jaroslav Tulach
103 public final class BrowserBuilder {
104 private static final Logger LOG = Logger.getLogger(BrowserBuilder.class.getName());
106 private String resource;
107 private Class<?> clazz;
108 private Class[] browserClass;
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)}.
124 * @param context any instances that should be available to the builder -
125 * implementation dependant
126 * @return new browser builder
128 public static BrowserBuilder newBrowser(Object... context) {
129 return new BrowserBuilder(context);
132 /** The class to load when the browser is initialized. This class
133 * is loaded by a special classloader (that supports {@link JavaScriptBody}
136 * @param mainClass the class to load and resolve when the browser is ready
137 * @return this builder
139 public BrowserBuilder loadClass(Class<?> mainClass) {
140 this.clazz = mainClass;
144 /** Allows one to specify a runnable that should be invoked when a load
145 * of a page is finished. This method may be used in addition or instead
146 * of {@link #loadClass(java.lang.Class)} and
147 * {@link #invoke(java.lang.String, java.lang.String...)} methods.
149 * @param r the code to run when the page is loaded
150 * @return this builder
153 public BrowserBuilder loadFinished(Runnable r) {
158 /** Page to load into the browser. If the <code>page</code> represents
159 * a {@link URL} known to the Java system, the URL is passed to the browser.
160 * If system property <code>browser.rootdir</code> is specified, then a
161 * file <code>page</code> relative to this directory is used as the URL.
162 * If no such file exists, the system seeks for the
163 * resource via {@link Class#getResource(java.lang.String)}
164 * method (relative to the {@link #loadClass(java.lang.Class) specified class}).
165 * If such resource is not found, a file relative to the location JAR
166 * that contains the {@link #loadClass(java.lang.Class) main class} is
169 * The search honors provided {@link #locale}, if specified.
170 * E.g. it will prefer <code>index_cs.html</code> over <code>index.html</code>
171 * if the locale is set to <code>cs_CZ</code>.
173 * @param page the location (relative, absolute, or URL) of a page to load
174 * @return this builder
176 public BrowserBuilder loadPage(String page) {
177 this.resource = page;
181 /** Locale to use when searching for an initial {@link #loadPage(java.lang.String) page to load}.
182 * Localization is best done by providing different versions of the
183 * initial page with appropriate suffixes (like <code>index_cs.html</code>).
184 * Then one can call this method with value of {@link Locale#getDefault()}
185 * to instruct the builder to use the user's current locale.
187 * @param locale the locale to use or <code>null</code> if no suffix search should be performed
188 * @return this builder
191 public BrowserBuilder locale(Locale locale) {
192 this.locale = locale;
196 /** Specifies callback method to notify the application that the browser is ready.
197 * There should be a <b>public static</b> method in the class specified
198 * by {@link #loadClass(java.lang.Class)} which takes an array of {@link String}
199 * argument. The method is called on the browser dispatch thread one
200 * the browser finishes loading of the {@link #loadPage(java.lang.String) HTML page}.
202 * @param methodName name of a method to seek for
203 * @param args parameters to pass to the method
204 * @return this builder
206 public BrowserBuilder invoke(String methodName, String... args) {
207 this.methodName = methodName;
208 this.methodArgs = args;
212 /** Loader to use when searching for classes to initialize.
213 * If specified, this loader is going to be used to load {@link Fn.Presenter}
214 * and {@link Contexts#fillInByProviders(java.lang.Class, org.netbeans.html.context.spi.Contexts.Builder) fill} {@link BrwsrCtx} in.
215 * Specifying special classloader may be useful in modular systems,
216 * like OSGi, where one needs to load classes from many otherwise independent
219 * @param l the loader to use (or <code>null</code>)
220 * @return this builder
223 public BrowserBuilder classloader(ClassLoader l) {
228 /** Shows the browser, loads specified page in and executes the
229 * {@link #invoke(java.lang.String, java.lang.String[]) initialization method}.
230 * The method returns when the browser is closed.
232 * @throws NullPointerException if some of essential parameters (like {@link #loadPage(java.lang.String) page} or
233 * {@link #loadClass(java.lang.Class) class} have not been specified
235 public void showAndWait() {
236 if (resource == null) {
237 throw new NullPointerException("Need to specify resource via loadPage method");
240 final Class<?> myCls;
243 } else if (onLoad != null) {
244 myCls = onLoad.getClass();
246 throw new NullPointerException("loadClass, neither loadFinished was called!");
248 IOException mal[] = { null };
249 URL url = findLocalizedResourceURL(resource, locale, mal, myCls);
251 Fn.Presenter dfnr = null;
252 for (Object o : context) {
253 if (o instanceof Fn.Presenter) {
254 dfnr = (Fn.Presenter)o;
259 if (dfnr == null && loader != null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class, loader)) {
264 if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) {
270 throw new IllegalStateException("Can't find any Fn.Presenter");
273 final ClassLoader activeLoader;
274 if (loader != null) {
275 if (!FnUtils.isJavaScriptCapable(loader)) {
276 throw new IllegalStateException("Loader cannot resolve @JavaScriptBody: " + loader);
278 activeLoader = loader;
279 } else if (FnUtils.isJavaScriptCapable(myCls.getClassLoader())) {
280 activeLoader = myCls.getClassLoader();
282 FImpl impl = new FImpl(myCls.getClassLoader());
283 activeLoader = FnUtils.newLoader(impl, dfnr, myCls.getClassLoader().getParent());
286 final Fn.Presenter dP = dfnr;
288 class OnPageLoad implements Runnable {
292 final Fn.Presenter aP = Fn.activePresenter();
293 final Fn.Presenter currentP = aP != null ? aP : dP;
295 Thread.currentThread().setContextClassLoader(activeLoader);
296 final Class<?> newClazz = Class.forName(myCls.getName(), true, activeLoader);
297 if (browserClass != null) {
298 browserClass[0] = newClazz;
300 Contexts.Builder cb = Contexts.newBuilder();
301 if (!Contexts.fillInByProviders(newClazz, cb)) {
302 LOG.log(Level.WARNING, "Using empty technology for {0}", newClazz);
304 if (currentP instanceof Executor) {
305 cb.register(Executor.class, (Executor)currentP, 1000);
307 cb.register(Fn.Presenter.class, currentP, 1000);
308 BrwsrCtx c = cb.build();
310 class CallInitMethod implements Runnable {
313 Throwable firstError = null;
314 if (onLoad != null) {
316 FnContext.currentPresenter(currentP);
318 } catch (Throwable ex) {
321 FnContext.currentPresenter(null);
324 INIT: if (methodName != null) {
325 if (methodArgs.length == 0) {
327 Method m = newClazz.getMethod(methodName);
328 FnContext.currentPresenter(currentP);
332 } catch (Throwable ex) {
335 FnContext.currentPresenter(null);
339 Method m = newClazz.getMethod(methodName, String[].class);
340 FnContext.currentPresenter(currentP);
341 m.invoke(m, (Object) methodArgs);
343 } catch (Throwable ex) {
344 LOG.log(Level.SEVERE, "Can't call " + methodName + " with args " + Arrays.toString(methodArgs), ex);
346 FnContext.currentPresenter(null);
349 if (firstError != null) {
350 LOG.log(Level.SEVERE, "Can't initialize the view", firstError);
355 c.execute(new CallInitMethod());
356 } catch (ClassNotFoundException ex) {
357 LOG.log(Level.SEVERE, "Can't load " + myCls.getName(), ex);
361 dfnr.displayPage(url, new OnPageLoad());
364 private static URL findResourceURL(String resource, String suffix, IOException[] mal, Class<?> relativeTo) {
365 if (suffix != null) {
366 int lastDot = resource.lastIndexOf('.');
368 resource = resource.substring(0, lastDot) + suffix + resource.substring(lastDot);
370 resource = resource + suffix;
376 String baseURL = System.getProperty("browser.rootdir"); // NOI18N
377 if (baseURL != null) {
378 URL u = new File(baseURL, resource).toURI().toURL();
385 URL u = new URL(resource);
386 if (suffix == null || isReal(u)) {
391 } catch (MalformedURLException ex) {
396 url = relativeTo.getResource(resource);
399 final ProtectionDomain pd = relativeTo.getProtectionDomain();
400 if (pd != null && pd.getCodeSource() != null) {
401 URL jar = pd.getCodeSource().getLocation();
403 URL u = new URL(jar, resource);
407 } catch (MalformedURLException ex) {
408 ex.initCause(mal[0]);
414 URL res = BrowserBuilder.class.getResource("html4j.txt");
415 LOG.log(Level.FINE, "Found html4j {0}", res);
418 URLConnection c = res.openConnection();
419 LOG.log(Level.FINE, "testing : {0}", c);
420 if (c instanceof JarURLConnection) {
421 JarURLConnection jc = (JarURLConnection) c;
422 URL base = jc.getJarFileURL();
423 for (int i = 0; i < 50; i++) {
424 URL u = new URL(base, resource);
429 base = new URL(base, "..");
432 } catch (IOException ex) {
440 static URL findLocalizedResourceURL(String resource, Locale l, IOException[] mal, Class<?> relativeTo) {
443 url = findResourceURL(resource, "_" + l.getLanguage() + "_" + l.getCountry(), mal, relativeTo);
447 url = findResourceURL(resource, "_" + l.getLanguage(), mal, relativeTo);
452 return findResourceURL(resource, null, mal, relativeTo);
455 private static boolean isReal(URL u) {
457 URLConnection conn = u.openConnection();
458 if (conn instanceof HttpURLConnection) {
459 HttpURLConnection hc = (HttpURLConnection) conn;
460 hc.setReadTimeout(5000);
461 if (hc.getResponseCode() >= 300) {
462 throw new IOException("Wrong code: " + hc.getResponseCode());
465 InputStream is = conn.getInputStream();
467 LOG.log(Level.FINE, "found real url: {0}", u);
469 } catch (IOException ignore) {
470 LOG.log(Level.FINE, "Cannot open " + u, ignore);
475 private static final class FImpl implements FindResources {
478 public FImpl(ClassLoader l) {
483 public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
485 URL u = l.getResource(path);
491 Enumeration<URL> en = l.getResources(path);
492 while (en.hasMoreElements()) {
493 results.add(en.nextElement());
495 } catch (IOException ex) {