All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.html.boot.BrowserBuilder Maven / Gradle / Ivy

There is a newer version: 1.8.1
Show newest version
/**
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
 *
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 */
package net.java.html.boot;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ServiceLoader;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.java.html.BrwsrCtx;
import net.java.html.js.JavaScriptBody;
import org.netbeans.html.boot.spi.Fn;
import org.netbeans.html.boot.spi.Fn.Presenter;
import org.netbeans.html.context.spi.Contexts;
import org.netbeans.html.context.spi.Contexts.Id;
import org.netbeans.html.boot.impl.FindResources;
import org.netbeans.html.boot.impl.FnContext;

/** Use this builder to launch your Java/HTML based application. Typical
 * usage in a main method of your application looks like this: 
 * 
 * 
 * public static void main(String... args) {
 *     BrowserBuilder.{@link #newBrowser newBrowser()}.
 *          {@link #loadClass(java.lang.Class) loadClass(YourMain.class)}.
 *          {@link #loadPage(java.lang.String) loadPage("index.html")}.
 *          {@link #locale(java.util.Locale) locale}({@link Locale#getDefault()}).
 *          {@link #invoke(java.lang.String, java.lang.String[]) invoke("initialized", args)}.
 *          {@link #showAndWait()};
 *     System.exit(0);
 * }
 * 
* The above will load YourMain class via * a special classloader, it will locate an index.html (relative * to YourMain class) and show it in a browser window. When the * initialization is over, a public static method initialized * in YourMain will be called with provided string parameters. *

* This module provides only API for building browsers. To use it properly one * also needs an implementation on the classpath of one's application. For example * use:

 * <dependency>
 *   <groupId>org.netbeans.html</groupId>
 *   <artifactId>net.java.html.boot.fx</artifactId>
 *   <scope>runtime</scope>
 * </dependency>
 * 
* * @author Jaroslav Tulach */ public final class BrowserBuilder { private static final Logger LOG = Logger.getLogger(BrowserBuilder.class.getName()); private String resource; private Class clazz; private Runnable onLoad; private String methodName; private String[] methodArgs; private final Object[] context; private ClassLoader loader; private Locale locale; private BrowserBuilder(Object[] context) { this.context = context; } /** Entry method to obtain a new browser builder. Follow by calling * its instance methods like {@link #loadClass(java.lang.Class)} and * {@link #loadPage(java.lang.String)}. * Since introduction of {@link Id technology identifiers} the * provided context objects are also passed to the * {@link BrwsrCtx context} when it is being * {@link Contexts#newBuilder(java.lang.Object...) created} * and can influence the selection * of available technologies * (like {@link org.netbeans.html.json.spi.Technology}, * {@link org.netbeans.html.json.spi.Transfer} or * {@link org.netbeans.html.json.spi.WSTransfer}) by name. * * @param context any instances that should be available to the builder - * implementation dependant * @return new browser builder */ public static BrowserBuilder newBrowser(Object... context) { return new BrowserBuilder(context); } /** The class to load when the browser is initialized. This class * is loaded by a special classloader (that supports {@link JavaScriptBody} * and co.). * * @param mainClass the class to load and resolve when the browser is ready * @return this builder */ public BrowserBuilder loadClass(Class mainClass) { this.clazz = mainClass; return this; } /** Allows one to specify a runnable that should be invoked when a load * of a page is finished. This method may be used in addition or instead * of {@link #loadClass(java.lang.Class)} and * {@link #invoke(java.lang.String, java.lang.String...)} methods. * * @param r the code to run when the page is loaded * @return this builder * @since 0.8.1 */ public BrowserBuilder loadFinished(Runnable r) { this.onLoad = r; return this; } /** Page to load into the browser. If the page represents * a {@link URL} known to the Java system, the URL is passed to the browser. * If system property browser.rootdir is specified, then a * file page relative to this directory is used as the URL. * If no such file exists, the system seeks for the * resource via {@link Class#getResource(java.lang.String)} * method (relative to the {@link #loadClass(java.lang.Class) specified class}). * If such resource is not found, a file relative to the location JAR * that contains the {@link #loadClass(java.lang.Class) main class} is * searched for. *

* The search honors provided {@link #locale}, if specified. * E.g. it will prefer index_cs.html over index.html * if the locale is set to cs_CZ. * * @param page the location (relative, absolute, or URL) of a page to load * @return this builder */ public BrowserBuilder loadPage(String page) { this.resource = page; return this; } /** Locale to use when searching for an initial {@link #loadPage(java.lang.String) page to load}. * Localization is best done by providing different versions of the * initial page with appropriate suffixes (like index_cs.html). * Then one can call this method with value of {@link Locale#getDefault()} * to instruct the builder to use the user's current locale. * * @param locale the locale to use or null if no suffix search should be performed * @return this builder * @since 1.0 */ public BrowserBuilder locale(Locale locale) { this.locale = locale; return this; } /** Specifies callback method to notify the application that the browser is ready. * There should be a public static method in the class specified * by {@link #loadClass(java.lang.Class)} which takes an array of {@link String} * argument. The method is called on the browser dispatch thread one * the browser finishes loading of the {@link #loadPage(java.lang.String) HTML page}. * * @param methodName name of a method to seek for * @param args parameters to pass to the method * @return this builder */ public BrowserBuilder invoke(String methodName, String... args) { this.methodName = methodName; this.methodArgs = args; return this; } /** Loader to use when searching for classes to initialize. * If specified, this loader is going to be used to load {@link Presenter} * and {@link Contexts#fillInByProviders(java.lang.Class, org.netbeans.html.context.spi.Contexts.Builder) fill} {@link BrwsrCtx} in. * Specifying special classloader may be useful in modular systems, * like OSGi, where one needs to load classes from many otherwise independent * modules. * * @param l the loader to use (or null) * @return this builder * @since 0.9 */ public BrowserBuilder classloader(ClassLoader l) { this.loader = l; return this; } /** Shows the browser, loads specified page in and executes the * {@link #invoke(java.lang.String, java.lang.String[]) initialization method}. * The method returns when the browser is closed. * * @throws NullPointerException if some of essential parameters (like {@link #loadPage(java.lang.String) page} or * {@link #loadClass(java.lang.Class) class} have not been specified */ public void showAndWait() { if (resource == null) { throw new NullPointerException("Need to specify resource via loadPage method"); } final Class myCls; if (clazz != null) { myCls = clazz; } else if (onLoad != null) { myCls = onLoad.getClass(); } else { throw new NullPointerException("loadClass, neither loadFinished was called!"); } IOException mal[] = { null }; URL url = findLocalizedResourceURL(resource, locale, mal, myCls); Fn.Presenter dfnr = null; for (Object o : context) { if (o instanceof Fn.Presenter) { dfnr = (Fn.Presenter)o; break; } } if (dfnr == null && loader != null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class, loader)) { dfnr = o; break; } if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) { dfnr = o; break; } if (dfnr == null) { throw new IllegalStateException("Can't find any Fn.Presenter"); } final ClassLoader activeLoader; if (loader != null) { final URL res = FnContext.isJavaScriptCapable(loader); if (res != null) { throw new IllegalStateException("Loader " + loader + " cannot resolve @JavaScriptBody, because of " + res ); } activeLoader = loader; } else { final URL res = FnContext.isJavaScriptCapable(myCls.getClassLoader()); if (res == null) { activeLoader = myCls.getClassLoader(); } else { FImpl impl = new FImpl(myCls.getClassLoader()); activeLoader = FnContext.newLoader(res, impl, dfnr, myCls.getClassLoader().getParent()); if (activeLoader == null) { throw new IllegalStateException("Cannot find asm-5.0.jar classes!"); } } } final Fn.Presenter dP = dfnr; class OnPageLoad implements Runnable { @Override public void run() { try { final Fn.Presenter aP = Fn.activePresenter(); final Fn.Presenter currentP = aP != null ? aP : dP; Thread.currentThread().setContextClassLoader(activeLoader); final Class newClazz = onLoad != null ? myCls : Class.forName(myCls.getName(), true, activeLoader); Contexts.Builder cb = Contexts.newBuilder(context); if (!Contexts.fillInByProviders(newClazz, cb)) { LOG.log(Level.WARNING, "Using empty technology for {0}", newClazz); } if (currentP instanceof Executor) { cb.register(Executor.class, (Executor)currentP, 1000); } cb.register(Fn.Presenter.class, currentP, 1000); BrwsrCtx c = cb.build(); class CallInitMethod implements Runnable { @Override public void run() { Throwable firstError = null; if (onLoad != null) { try { FnContext.currentPresenter(currentP); onLoad.run(); } catch (Throwable ex) { firstError = ex; } finally { FnContext.currentPresenter(null); } } INIT: if (methodName != null) { if (methodArgs.length == 0) { try { Method m = newClazz.getMethod(methodName); FnContext.currentPresenter(currentP); m.invoke(null); firstError = null; break INIT; } catch (Throwable ex) { firstError = ex; } finally { FnContext.currentPresenter(null); } } try { Method m = newClazz.getMethod(methodName, String[].class); FnContext.currentPresenter(currentP); m.invoke(m, (Object) methodArgs); firstError = null; } catch (Throwable ex) { LOG.log(Level.SEVERE, "Can't call " + methodName + " with args " + Arrays.toString(methodArgs), ex); } finally { FnContext.currentPresenter(null); } } if (firstError != null) { LOG.log(Level.SEVERE, "Can't initialize the view", firstError); } } } c.execute(new CallInitMethod()); } catch (ClassNotFoundException ex) { LOG.log(Level.SEVERE, "Can't load " + myCls.getName(), ex); } } } dfnr.displayPage(url, new OnPageLoad()); } private static URL findResourceURL(String resource, String suffix, IOException[] mal, Class relativeTo) { if (suffix != null) { int lastDot = resource.lastIndexOf('.'); if (lastDot != -1) { resource = resource.substring(0, lastDot) + suffix + resource.substring(lastDot); } else { resource = resource + suffix; } } URL url = null; try { String baseURL = System.getProperty("browser.rootdir"); // NOI18N if (baseURL != null) { URL u = new File(baseURL, resource).toURI().toURL(); if (isReal(u)) { url = u; } } { URL u = new URL(resource); if (suffix == null || isReal(u)) { url = u; } return url; } } catch (MalformedURLException ex) { mal[0] = ex; } if (url == null) { url = relativeTo.getResource(resource); } if (url == null) { final ProtectionDomain pd = relativeTo.getProtectionDomain(); if (pd != null && pd.getCodeSource() != null) { URL jar = pd.getCodeSource().getLocation(); try { URL u = new URL(jar, resource); if (isReal(u)) { url = u; } } catch (MalformedURLException ex) { ex.initCause(mal[0]); mal[0] = ex; } } } if (url == null) { URL res = BrowserBuilder.class.getResource("html4j.txt"); LOG.log(Level.FINE, "Found html4j {0}", res); if (res != null) { try { URLConnection c = res.openConnection(); LOG.log(Level.FINE, "testing : {0}", c); if (c instanceof JarURLConnection) { JarURLConnection jc = (JarURLConnection) c; URL base = jc.getJarFileURL(); for (int i = 0; i < 50; i++) { URL u = new URL(base, resource); if (isReal(u)) { url = u; break; } base = new URL(base, ".."); } } } catch (IOException ex) { mal[0] = ex; } } } return url; } static URL findLocalizedResourceURL(String resource, Locale l, IOException[] mal, Class relativeTo) { URL url = null; if (l != null) { url = findResourceURL(resource, "_" + l.getLanguage() + "_" + l.getCountry(), mal, relativeTo); if (url != null) { return url; } url = findResourceURL(resource, "_" + l.getLanguage(), mal, relativeTo); } if (url != null) { return url; } return findResourceURL(resource, null, mal, relativeTo); } private static boolean isReal(URL u) { try { URLConnection conn = u.openConnection(); if (conn instanceof HttpURLConnection) { HttpURLConnection hc = (HttpURLConnection) conn; hc.setReadTimeout(5000); if (hc.getResponseCode() >= 300) { throw new IOException("Wrong code: " + hc.getResponseCode()); } } InputStream is = conn.getInputStream(); is.close(); LOG.log(Level.FINE, "found real url: {0}", u); return true; } catch (IOException ignore) { LOG.log(Level.FINE, "Cannot open " + u, ignore); return false; } } private static final class FImpl implements FindResources { final ClassLoader l; public FImpl(ClassLoader l) { this.l = l; } @Override public void findResources(String path, Collection results, boolean oneIsEnough) { if (oneIsEnough) { URL u = l.getResource(path); if (u != null) { results.add(u); } } else { try { Enumeration en = l.getResources(path); while (en.hasMoreElements()) { results.add(en.nextElement()); } } catch (IOException ex) { // no results } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy