boot/src/main/java/net/java/html/boot/BrowserBuilder.java
author Jaroslav Tulach <jtulach@netbeans.org>
Tue, 26 Aug 2014 18:13:30 +0200
changeset 838 bdc3d696dd4a
parent 836 50acbae0804c
child 882 0faee341d5b5
permissions -rw-r--r--
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.
jaroslav@123
     1
/**
jaroslav@358
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
jaroslav@123
     3
 *
jaroslav@551
     4
 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
jaroslav@123
     5
 *
jaroslav@358
     6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
jaroslav@358
     7
 * Other names may be trademarks of their respective owners.
jaroslav@123
     8
 *
jaroslav@358
     9
 * The contents of this file are subject to the terms of either the GNU
jaroslav@358
    10
 * General Public License Version 2 only ("GPL") or the Common
jaroslav@358
    11
 * Development and Distribution License("CDDL") (collectively, the
jaroslav@358
    12
 * "License"). You may not use this file except in compliance with the
jaroslav@358
    13
 * License. You can obtain a copy of the License at
jaroslav@358
    14
 * http://www.netbeans.org/cddl-gplv2.html
jaroslav@358
    15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
jaroslav@358
    16
 * specific language governing permissions and limitations under the
jaroslav@358
    17
 * License.  When distributing the software, include this License Header
jaroslav@358
    18
 * Notice in each file and include the License file at
jaroslav@358
    19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
jaroslav@358
    20
 * particular file as subject to the "Classpath" exception as provided
jaroslav@358
    21
 * by Oracle in the GPL Version 2 section of the License file that
jaroslav@358
    22
 * accompanied this code. If applicable, add the following below the
jaroslav@358
    23
 * License Header, with the fields enclosed by brackets [] replaced by
jaroslav@358
    24
 * your own identifying information:
jaroslav@358
    25
 * "Portions Copyrighted [year] [name of copyright owner]"
jaroslav@358
    26
 *
jaroslav@358
    27
 * Contributor(s):
jaroslav@358
    28
 *
jaroslav@358
    29
 * The Original Software is NetBeans. The Initial Developer of the Original
jaroslav@551
    30
 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
jaroslav@358
    31
 *
jaroslav@358
    32
 * If you wish your version of this file to be governed by only the CDDL
jaroslav@358
    33
 * or only the GPL Version 2, indicate your decision by adding
jaroslav@358
    34
 * "[Contributor] elects to include this software in this distribution
jaroslav@358
    35
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
jaroslav@358
    36
 * single choice of license, a recipient has the option to distribute
jaroslav@358
    37
 * your version of this file under either the CDDL, the GPL Version 2 or
jaroslav@358
    38
 * to extend the choice of license to its licensees as provided above.
jaroslav@358
    39
 * However, if you add GPL Version 2 code and therefore, elected the GPL
jaroslav@358
    40
 * Version 2 license, then the option applies only if the new code is
jaroslav@358
    41
 * made subject to such option by the copyright holder.
jaroslav@123
    42
 */
jaroslav@123
    43
package net.java.html.boot;
jaroslav@123
    44
jaroslav@202
    45
import java.io.File;
jaroslav@127
    46
import java.io.IOException;
jtulach@653
    47
import java.io.InputStream;
jaroslav@156
    48
import java.lang.reflect.Method;
jtulach@834
    49
import java.net.HttpURLConnection;
jtulach@653
    50
import java.net.JarURLConnection;
jaroslav@148
    51
import java.net.MalformedURLException;
jaroslav@127
    52
import java.net.URL;
jtulach@653
    53
import java.net.URLConnection;
jtulach@653
    54
import java.security.ProtectionDomain;
jaroslav@156
    55
import java.util.Arrays;
jaroslav@127
    56
import java.util.Collection;
jaroslav@127
    57
import java.util.Enumeration;
jtulach@834
    58
import java.util.Locale;
jaroslav@127
    59
import java.util.ServiceLoader;
jaroslav@574
    60
import java.util.concurrent.Executor;
jaroslav@156
    61
import java.util.logging.Level;
jaroslav@156
    62
import java.util.logging.Logger;
jtulach@569
    63
import net.java.html.BrwsrCtx;
jaroslav@166
    64
import net.java.html.js.JavaScriptBody;
jtulach@838
    65
import org.netbeans.html.boot.spi.Fn;
jtulach@838
    66
import org.netbeans.html.context.spi.Contexts;
jaroslav@362
    67
import org.netbeans.html.boot.impl.FindResources;
jaroslav@362
    68
import org.netbeans.html.boot.impl.FnContext;
jaroslav@509
    69
import org.netbeans.html.boot.impl.FnUtils;
jaroslav@127
    70
jaroslav@166
    71
/** Use this builder to launch your Java/HTML based application. Typical
jaroslav@166
    72
 * usage in a main method of your application looks like this: 
jaroslav@166
    73
 * <pre>
jaroslav@166
    74
 * 
jaroslav@166
    75
 * <b>public static void</b> <em>main</em>(String... args) {
jtulach@700
    76
 *     BrowserBuilder.{@link #newBrowser newBrowser()}.
jaroslav@166
    77
 *          {@link #loadClass(java.lang.Class) loadClass(YourMain.class)}.
jaroslav@166
    78
 *          {@link #loadPage(java.lang.String) loadPage("index.html")}.
jtulach@836
    79
 *          {@link #locale(java.util.Locale) locale}({@link Locale#getDefault()}).
jaroslav@166
    80
 *          {@link #invoke(java.lang.String, java.lang.String[]) invoke("initialized", args)}.
jaroslav@166
    81
 *          {@link #showAndWait()};
jaroslav@166
    82
 *     System.exit(0);
jaroslav@166
    83
 * }
jaroslav@166
    84
 * </pre>
jaroslav@166
    85
 * The above will load <code>YourMain</code> class via
jaroslav@166
    86
 * a special classloader, it will locate an <code>index.html</code> (relative
jaroslav@166
    87
 * to <code>YourMain</code> class) and show it in a browser window. When the
jaroslav@166
    88
 * initialization is over, a <b>public static</b> method <em>initialized</em>
jaroslav@166
    89
 * in <code>YourMain</code> will be called with provided string parameters.
jaroslav@166
    90
 * <p>
jaroslav@166
    91
 * This module provides only API for building browsers. To use it properly one
jaroslav@166
    92
 * also needs an implementation on the classpath of one's application. For example
jaroslav@166
    93
 * use: <pre>
jaroslav@166
    94
 * &lt;dependency&gt;
jaroslav@361
    95
 *   &lt;groupId&gt;org.netbeans.html&lt;/groupId&gt;
jaroslav@361
    96
 *   &lt;artifactId&gt;net.java.html.boot.fx&lt;/artifactId&gt;
jaroslav@166
    97
 *   &lt;scope&gt;runtime&lt;/scope&gt;
jaroslav@166
    98
 * &lt;/dependency&gt;
jaroslav@166
    99
 * </pre>
jaroslav@123
   100
 *
jtulach@790
   101
 * @author Jaroslav Tulach
jaroslav@123
   102
 */
jaroslav@123
   103
public final class BrowserBuilder {
jaroslav@156
   104
    private static final Logger LOG = Logger.getLogger(BrowserBuilder.class.getName());
jaroslav@156
   105
    
jaroslav@127
   106
    private String resource;
jaroslav@127
   107
    private Class<?> clazz;
jaroslav@146
   108
    private Class[] browserClass;
jaroslav@146
   109
    private Runnable onLoad;
jaroslav@156
   110
    private String methodName;
jaroslav@156
   111
    private String[] methodArgs;
jaroslav@288
   112
    private final Object[] context;
jtulach@771
   113
    private ClassLoader loader;
jtulach@834
   114
    private Locale locale;
jaroslav@156
   115
    
jaroslav@288
   116
    private BrowserBuilder(Object[] context) {
jaroslav@288
   117
        this.context = context;
jaroslav@123
   118
    }
jaroslav@166
   119
jaroslav@166
   120
    /** Entry method to obtain a new browser builder. Follow by calling 
jaroslav@166
   121
     * its instance methods like {@link #loadClass(java.lang.Class)} and
jaroslav@166
   122
     * {@link #loadPage(java.lang.String)}.
jaroslav@166
   123
     * 
jaroslav@288
   124
     * @param context any instances that should be available to the builder -
jaroslav@458
   125
     *   implementation dependant
jaroslav@166
   126
     * @return new browser builder
jaroslav@166
   127
     */
jaroslav@288
   128
    public static BrowserBuilder newBrowser(Object... context) {
jaroslav@288
   129
        return new BrowserBuilder(context);
jaroslav@123
   130
    }
jaroslav@123
   131
    
jaroslav@166
   132
    /** The class to load when the browser is initialized. This class
jaroslav@166
   133
     * is loaded by a special classloader (that supports {@link JavaScriptBody}
jaroslav@166
   134
     * and co.). 
jaroslav@166
   135
     * 
jaroslav@166
   136
     * @param mainClass the class to load and resolve when the browser is ready
jaroslav@166
   137
     * @return this builder
jaroslav@166
   138
     */
jaroslav@127
   139
    public BrowserBuilder loadClass(Class<?> mainClass) {
jaroslav@127
   140
        this.clazz = mainClass;
jaroslav@123
   141
        return this;
jaroslav@123
   142
    }
jtulach@656
   143
    
jtulach@656
   144
    /** Allows one to specify a runnable that should be invoked when a load
jtulach@656
   145
     * of a page is finished. This method may be used in addition or instead
jtulach@656
   146
     * of {@link #loadClass(java.lang.Class)} and 
jtulach@656
   147
     * {@link #invoke(java.lang.String, java.lang.String...)} methods.
jtulach@656
   148
     * 
jtulach@656
   149
     * @param r the code to run when the page is loaded
jtulach@656
   150
     * @return this builder
jtulach@656
   151
     * @since 0.8.1
jtulach@656
   152
     */
jtulach@656
   153
    public BrowserBuilder loadFinished(Runnable r) {
jtulach@656
   154
        this.onLoad = r;
jtulach@656
   155
        return this;
jtulach@656
   156
    }
jaroslav@156
   157
jaroslav@166
   158
    /** Page to load into the browser. If the <code>page</code> represents
jaroslav@166
   159
     * a {@link URL} known to the Java system, the URL is passed to the browser. 
jaroslav@202
   160
     * If system property <code>browser.rootdir</code> is specified, then a
jaroslav@202
   161
     * file <code>page</code> relative to this directory is used as the URL.
jaroslav@202
   162
     * If no such file exists, the system seeks for the 
jaroslav@202
   163
     * resource via {@link Class#getResource(java.lang.String)}
jaroslav@202
   164
     * method (relative to the {@link #loadClass(java.lang.Class) specified class}). 
jaroslav@202
   165
     * If such resource is not found, a file relative to the location JAR
jaroslav@202
   166
     * that contains the {@link #loadClass(java.lang.Class) main class} is 
jaroslav@202
   167
     * searched for.
jtulach@834
   168
     * <p>
jtulach@834
   169
     * The search honors provided {@link #locale}, if specified.
jtulach@834
   170
     * E.g. it will prefer <code>index_cs.html</code> over <code>index.html</code>
jtulach@834
   171
     * if the locale is set to <code>cs_CZ</code>.
jaroslav@166
   172
     * 
jaroslav@166
   173
     * @param page the location (relative, absolute, or URL) of a page to load
jtulach@834
   174
     * @return this builder
jaroslav@166
   175
     */
jaroslav@166
   176
    public BrowserBuilder loadPage(String page) {
jaroslav@166
   177
        this.resource = page;
jaroslav@166
   178
        return this;
jaroslav@166
   179
    }
jaroslav@166
   180
    
jtulach@834
   181
    /** Locale to use when searching for an initial {@link #loadPage(java.lang.String) page to load}.
jtulach@834
   182
     * Localization is best done by providing different versions of the 
jtulach@834
   183
     * initial page with appropriate suffixes (like <code>index_cs.html</code>).
jtulach@834
   184
     * Then one can call this method with value of {@link Locale#getDefault()}
jtulach@834
   185
     * to instruct the builder to use the user's current locale.
jtulach@834
   186
     * 
jtulach@834
   187
     * @param locale the locale to use or <code>null</code> if no suffix search should be performed
jtulach@834
   188
     * @return this builder
jtulach@834
   189
     * @since 1.0
jtulach@834
   190
     */
jtulach@834
   191
    public BrowserBuilder locale(Locale locale) {
jtulach@834
   192
        this.locale = locale;
jtulach@834
   193
        return this;
jtulach@834
   194
    }
jtulach@834
   195
    
jaroslav@166
   196
    /** Specifies callback method to notify the application that the browser is ready.
jaroslav@166
   197
     * There should be a <b>public static</b> method in the class specified
jaroslav@166
   198
     * by {@link #loadClass(java.lang.Class)} which takes an array of {@link String}
jaroslav@166
   199
     * argument. The method is called on the browser dispatch thread one
jaroslav@166
   200
     * the browser finishes loading of the {@link #loadPage(java.lang.String) HTML page}.
jaroslav@166
   201
     * 
jaroslav@166
   202
     * @param methodName name of a method to seek for
jaroslav@166
   203
     * @param args parameters to pass to the method
jaroslav@166
   204
     * @return this builder
jaroslav@166
   205
     */
jaroslav@156
   206
    public BrowserBuilder invoke(String methodName, String... args) {
jaroslav@156
   207
        this.methodName = methodName;
jaroslav@156
   208
        this.methodArgs = args;
jaroslav@156
   209
        return this;
jaroslav@156
   210
    }
jaroslav@166
   211
jtulach@771
   212
    /** Loader to use when searching for classes to initialize. 
jtulach@771
   213
     * If specified, this loader is going to be used to load {@link Fn.Presenter}
jtulach@838
   214
     * and {@link Contexts#fillInByProviders(java.lang.Class, org.netbeans.html.context.spi.Contexts.Builder) fill} {@link BrwsrCtx} in.
jtulach@771
   215
     * Specifying special classloader may be useful in modular systems, 
jtulach@771
   216
     * like OSGi, where one needs to load classes from many otherwise independent
jtulach@771
   217
     * modules.
jtulach@771
   218
     * 
jtulach@771
   219
     * @param l the loader to use (or <code>null</code>)
jtulach@771
   220
     * @return this builder
jtulach@771
   221
     * @since 0.9
jtulach@771
   222
     */
jtulach@771
   223
    public BrowserBuilder classloader(ClassLoader l) {
jtulach@771
   224
        this.loader = l;
jtulach@771
   225
        return this;
jtulach@771
   226
    }
jtulach@771
   227
jaroslav@166
   228
    /** Shows the browser, loads specified page in and executes the 
jaroslav@166
   229
     * {@link #invoke(java.lang.String, java.lang.String[]) initialization method}.
jaroslav@166
   230
     * The method returns when the browser is closed.
jaroslav@166
   231
     * 
jaroslav@166
   232
     * @throws NullPointerException if some of essential parameters (like {@link #loadPage(java.lang.String) page} or
jaroslav@166
   233
     *    {@link #loadClass(java.lang.Class) class} have not been specified
jaroslav@166
   234
     */
jaroslav@123
   235
    public void showAndWait() {
jaroslav@146
   236
        if (resource == null) {
jaroslav@166
   237
            throw new NullPointerException("Need to specify resource via loadPage method");
jaroslav@146
   238
        }
jaroslav@146
   239
        
jtulach@656
   240
        final Class<?> myCls;
jtulach@656
   241
        if (clazz != null) {
jtulach@656
   242
            myCls = clazz;
jtulach@656
   243
        } else if (onLoad != null) {
jtulach@656
   244
            myCls = onLoad.getClass();
jtulach@656
   245
        } else {
jtulach@656
   246
            throw new NullPointerException("loadClass, neither loadFinished was called!");
jtulach@656
   247
        }
jtulach@834
   248
        IOException mal[] = { null };
jtulach@834
   249
        URL url = findLocalizedResourceURL(resource, locale, mal, myCls);
jaroslav@288
   250
        
jaroslav@288
   251
        Fn.Presenter dfnr = null;
jaroslav@288
   252
        for (Object o : context) {
jaroslav@288
   253
            if (o instanceof Fn.Presenter) {
jaroslav@288
   254
                dfnr = (Fn.Presenter)o;
jaroslav@288
   255
                break;
jaroslav@288
   256
            }
jaroslav@288
   257
        }
jaroslav@127
   258
jtulach@771
   259
        if (dfnr == null && loader != null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class, loader)) {
jtulach@771
   260
            dfnr = o;
jtulach@771
   261
            break;
jtulach@771
   262
        }
jtulach@771
   263
        
jaroslav@288
   264
        if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) {
jaroslav@288
   265
            dfnr = o;
jaroslav@288
   266
            break;
jaroslav@288
   267
        }
jaroslav@288
   268
        
jaroslav@288
   269
        if (dfnr == null) {
jaroslav@288
   270
            throw new IllegalStateException("Can't find any Fn.Presenter");
jaroslav@288
   271
        }
jaroslav@288
   272
        
jtulach@771
   273
        final ClassLoader activeLoader;
jtulach@771
   274
        if (loader != null) {
jtulach@771
   275
            if (!FnUtils.isJavaScriptCapable(loader)) {
jtulach@771
   276
                throw new IllegalStateException("Loader cannot resolve @JavaScriptBody: " + loader);
jtulach@771
   277
            }
jtulach@771
   278
            activeLoader = loader;
jtulach@771
   279
        } else if (FnUtils.isJavaScriptCapable(myCls.getClassLoader())) {
jtulach@771
   280
            activeLoader = myCls.getClassLoader();
jaroslav@509
   281
        } else {
jtulach@656
   282
            FImpl impl = new FImpl(myCls.getClassLoader());
jtulach@771
   283
            activeLoader = FnUtils.newLoader(impl, dfnr, myCls.getClassLoader().getParent());
jaroslav@509
   284
        }
jtulach@736
   285
        
jtulach@736
   286
        final Fn.Presenter dP = dfnr;
jaroslav@128
   287
jaroslav@288
   288
        class OnPageLoad implements Runnable {
jaroslav@288
   289
            @Override
jaroslav@288
   290
            public void run() {
jaroslav@288
   291
                try {
jtulach@736
   292
                    final Fn.Presenter aP = Fn.activePresenter();
jtulach@736
   293
                    final Fn.Presenter currentP = aP != null ? aP : dP;
jtulach@736
   294
                    
jtulach@771
   295
                    Thread.currentThread().setContextClassLoader(activeLoader);
jtulach@771
   296
                    final Class<?> newClazz = Class.forName(myCls.getName(), true, activeLoader);
jtulach@736
   297
                    if (browserClass != null) {
jtulach@736
   298
                        browserClass[0] = newClazz;
jtulach@736
   299
                    }
jtulach@736
   300
                    Contexts.Builder cb = Contexts.newBuilder();
jtulach@736
   301
                    if (!Contexts.fillInByProviders(newClazz, cb)) {
jtulach@736
   302
                        LOG.log(Level.WARNING, "Using empty technology for {0}", newClazz);
jtulach@736
   303
                    }
jtulach@736
   304
                    if (currentP instanceof Executor) {
jtulach@736
   305
                        cb.register(Executor.class, (Executor)currentP, 1000);
jtulach@736
   306
                    }
jtulach@736
   307
                    cb.register(Fn.Presenter.class, currentP, 1000);
jtulach@736
   308
                    BrwsrCtx c = cb.build();
jtulach@736
   309
jtulach@736
   310
                    class CallInitMethod implements Runnable {
jtulach@736
   311
                        @Override
jtulach@736
   312
                        public void run() {
jtulach@736
   313
                            Throwable firstError = null;
jtulach@736
   314
                            if (onLoad != null) {
jtulach@736
   315
                                try {
jtulach@736
   316
                                    FnContext.currentPresenter(currentP);
jtulach@736
   317
                                    onLoad.run();
jtulach@736
   318
                                } catch (Throwable ex) {
jtulach@736
   319
                                    firstError = ex;
jtulach@736
   320
                                } finally {
jtulach@736
   321
                                    FnContext.currentPresenter(null);
jtulach@736
   322
                                }
jtulach@736
   323
                            }
jtulach@736
   324
                            INIT: if (methodName != null) {
jtulach@736
   325
                                if (methodArgs.length == 0) {
jtulach@736
   326
                                    try {
jtulach@736
   327
                                        Method m = newClazz.getMethod(methodName);
jtulach@736
   328
                                        FnContext.currentPresenter(currentP);
jtulach@736
   329
                                        m.invoke(null);
jtulach@736
   330
                                        firstError = null;
jtulach@736
   331
                                        break INIT;
jtulach@736
   332
                                    } catch (Throwable ex) {
jtulach@736
   333
                                        firstError = ex;
jtulach@736
   334
                                    } finally {
jtulach@736
   335
                                        FnContext.currentPresenter(null);
jtulach@736
   336
                                    }
jtulach@736
   337
                                }
jtulach@736
   338
                                try {
jtulach@736
   339
                                    Method m = newClazz.getMethod(methodName, String[].class);
jtulach@736
   340
                                    FnContext.currentPresenter(currentP);
jtulach@736
   341
                                    m.invoke(m, (Object) methodArgs);
jtulach@736
   342
                                    firstError = null;
jtulach@736
   343
                                } catch (Throwable ex) {
jtulach@736
   344
                                    LOG.log(Level.SEVERE, "Can't call " + methodName + " with args " + Arrays.toString(methodArgs), ex);
jtulach@736
   345
                                } finally {
jtulach@736
   346
                                    FnContext.currentPresenter(null);
jtulach@736
   347
                                }
jtulach@736
   348
                            }
jtulach@736
   349
                            if (firstError != null) {
jtulach@736
   350
                                LOG.log(Level.SEVERE, "Can't initialize the view", firstError);
jtulach@736
   351
                            }
jtulach@569
   352
                        }
jaroslav@288
   353
                    }
jtulach@569
   354
                    
jtulach@736
   355
                    c.execute(new CallInitMethod());
jaroslav@288
   356
                } catch (ClassNotFoundException ex) {
jtulach@656
   357
                    LOG.log(Level.SEVERE, "Can't load " + myCls.getName(), ex);
jaroslav@128
   358
                }
jaroslav@127
   359
            }
jaroslav@127
   360
        }
jaroslav@288
   361
        dfnr.displayPage(url, new OnPageLoad());
jaroslav@127
   362
    }
jaroslav@146
   363
jtulach@834
   364
    private static URL findResourceURL(String resource, String suffix, IOException[] mal, Class<?> relativeTo) {
jtulach@834
   365
        if (suffix != null) {
jtulach@834
   366
            int lastDot = resource.lastIndexOf('.');
jtulach@834
   367
            if (lastDot != -1) {
jtulach@834
   368
                resource = resource.substring(0, lastDot) + suffix + resource.substring(lastDot);
jtulach@834
   369
            } else {
jtulach@834
   370
                resource = resource + suffix;
jtulach@834
   371
            }
jtulach@834
   372
        }
jtulach@834
   373
        
jtulach@834
   374
        URL url = null;
jtulach@834
   375
        try {
jtulach@834
   376
            String baseURL = System.getProperty("browser.rootdir"); // NOI18N
jtulach@834
   377
            if (baseURL != null) {
jtulach@834
   378
                URL u = new File(baseURL, resource).toURI().toURL();
jtulach@834
   379
                if (isReal(u)) {
jtulach@834
   380
                    url = u;
jtulach@834
   381
                }
jtulach@834
   382
            } 
jtulach@834
   383
            
jtulach@834
   384
            {
jtulach@834
   385
                URL u = new URL(resource);
jtulach@834
   386
                if (suffix == null || isReal(u)) {
jtulach@834
   387
                    url = u;
jtulach@834
   388
                }
jtulach@834
   389
                return url;
jtulach@834
   390
            }
jtulach@834
   391
        } catch (MalformedURLException ex) {
jtulach@834
   392
            mal[0] = ex;
jtulach@834
   393
        }
jtulach@834
   394
        
jtulach@834
   395
        if (url == null) {
jtulach@834
   396
            url = relativeTo.getResource(resource);
jtulach@834
   397
        }
jtulach@834
   398
        if (url == null) {
jtulach@834
   399
            final ProtectionDomain pd = relativeTo.getProtectionDomain();
jtulach@834
   400
            if (pd != null && pd.getCodeSource() != null) {
jtulach@834
   401
                URL jar = pd.getCodeSource().getLocation();
jtulach@834
   402
                try {
jtulach@834
   403
                    URL u = new URL(jar, resource);
jtulach@834
   404
                    if (isReal(u)) {
jtulach@834
   405
                        url = u;
jtulach@834
   406
                    }
jtulach@834
   407
                } catch (MalformedURLException ex) {
jtulach@834
   408
                    ex.initCause(mal[0]);
jtulach@834
   409
                    mal[0] = ex;
jtulach@834
   410
                }
jtulach@834
   411
            }
jtulach@834
   412
        }
jtulach@834
   413
        if (url == null) {
jtulach@834
   414
            URL res = BrowserBuilder.class.getResource("html4j.txt");
jtulach@834
   415
            LOG.log(Level.FINE, "Found html4j {0}", res);
jtulach@834
   416
            if (res != null) {
jtulach@834
   417
                try {
jtulach@834
   418
                    URLConnection c = res.openConnection();
jtulach@834
   419
                    LOG.log(Level.FINE, "testing : {0}", c);
jtulach@834
   420
                    if (c instanceof JarURLConnection) {
jtulach@834
   421
                        JarURLConnection jc = (JarURLConnection) c;
jtulach@834
   422
                        URL base = jc.getJarFileURL();
jtulach@834
   423
                        for (int i = 0; i < 50; i++) {
jtulach@834
   424
                            URL u = new URL(base, resource);
jtulach@834
   425
                            if (isReal(u)) {
jtulach@834
   426
                                url = u;
jtulach@834
   427
                                break;
jtulach@834
   428
                            }
jtulach@834
   429
                            base = new URL(base, "..");
jtulach@834
   430
                        }
jtulach@834
   431
                    }
jtulach@834
   432
                } catch (IOException ex) {
jtulach@834
   433
                    mal[0] = ex;
jtulach@834
   434
                }
jtulach@834
   435
            }
jtulach@834
   436
        }
jtulach@834
   437
        return url;
jtulach@834
   438
    }
jtulach@834
   439
jtulach@834
   440
    static URL findLocalizedResourceURL(String resource, Locale l, IOException[] mal, Class<?> relativeTo) {
jtulach@834
   441
        URL url = null;
jtulach@834
   442
        if (l != null) {
jtulach@834
   443
            url = findResourceURL(resource, "_" + l.getLanguage() + "_" + l.getCountry(), mal, relativeTo);
jtulach@834
   444
            if (url != null) {
jtulach@834
   445
                return url;
jtulach@834
   446
            }
jtulach@834
   447
            url = findResourceURL(resource, "_" + l.getLanguage(), mal, relativeTo);
jtulach@834
   448
        }
jtulach@834
   449
        if (url != null) {
jtulach@834
   450
            return url;
jtulach@834
   451
        }
jtulach@834
   452
        return findResourceURL(resource, null, mal, relativeTo);
jtulach@834
   453
    }
jtulach@834
   454
    
jtulach@834
   455
    private static boolean isReal(URL u) {
jtulach@834
   456
        try {
jtulach@834
   457
            URLConnection conn = u.openConnection();
jtulach@834
   458
            if (conn instanceof HttpURLConnection) {
jtulach@834
   459
                HttpURLConnection hc = (HttpURLConnection) conn;
jtulach@834
   460
                hc.setReadTimeout(5000);
jtulach@834
   461
                if (hc.getResponseCode() >= 300) {
jtulach@834
   462
                    throw new IOException("Wrong code: " + hc.getResponseCode());
jtulach@834
   463
                }
jtulach@834
   464
            }
jtulach@834
   465
            InputStream is = conn.getInputStream();
jtulach@834
   466
            is.close();
jtulach@834
   467
            LOG.log(Level.FINE, "found real url: {0}", u);
jtulach@834
   468
            return true;
jtulach@834
   469
        } catch (IOException ignore) {
jtulach@834
   470
            LOG.log(Level.FINE, "Cannot open " + u, ignore);
jtulach@834
   471
            return false;
jtulach@834
   472
        }
jtulach@834
   473
    }
jtulach@834
   474
jaroslav@128
   475
    private static final class FImpl implements FindResources {
jaroslav@128
   476
        final ClassLoader l;
jaroslav@127
   477
jaroslav@127
   478
        public FImpl(ClassLoader l) {
jaroslav@127
   479
            this.l = l;
jaroslav@127
   480
        }
jaroslav@127
   481
jaroslav@127
   482
        @Override
jaroslav@127
   483
        public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
jaroslav@127
   484
            if (oneIsEnough) {
jaroslav@127
   485
                URL u = l.getResource(path);
jaroslav@127
   486
                if (u != null) {
jaroslav@127
   487
                    results.add(u);
jaroslav@127
   488
                }
jaroslav@127
   489
            } else {
jaroslav@127
   490
                try {
jaroslav@127
   491
                    Enumeration<URL> en = l.getResources(path);
jaroslav@127
   492
                    while (en.hasMoreElements()) {
jaroslav@127
   493
                        results.add(en.nextElement());
jaroslav@127
   494
                    }
jaroslav@127
   495
                } catch (IOException ex) {
jaroslav@127
   496
                    // no results
jaroslav@127
   497
                }
jaroslav@127
   498
            }
jaroslav@127
   499
        }
jaroslav@127
   500
        
jaroslav@123
   501
    }
jaroslav@123
   502
}