boot/src/main/java/net/java/html/boot/BrowserBuilder.java
author Jaroslav Tulach <jtulach@netbeans.org>
Thu, 21 Jan 2016 18:54:01 +0100
changeset 1045 f152fe9735f0
parent 932 f2de2ae88589
permissions -rw-r--r--
Allow use of lamda functions in onLoad callback. Lambda function classes cannot be loaded by name, so avoid doing so when onLoad is specified.
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@882
    66
import org.netbeans.html.boot.spi.Fn.Presenter;
jtulach@838
    67
import org.netbeans.html.context.spi.Contexts;
jtulach@886
    68
import org.netbeans.html.context.spi.Contexts.Id;
jaroslav@362
    69
import org.netbeans.html.boot.impl.FindResources;
jaroslav@362
    70
import org.netbeans.html.boot.impl.FnContext;
jaroslav@127
    71
jaroslav@166
    72
/** Use this builder to launch your Java/HTML based application. Typical
jaroslav@166
    73
 * usage in a main method of your application looks like this: 
jaroslav@166
    74
 * <pre>
jaroslav@166
    75
 * 
jaroslav@166
    76
 * <b>public static void</b> <em>main</em>(String... args) {
jtulach@700
    77
 *     BrowserBuilder.{@link #newBrowser newBrowser()}.
jaroslav@166
    78
 *          {@link #loadClass(java.lang.Class) loadClass(YourMain.class)}.
jaroslav@166
    79
 *          {@link #loadPage(java.lang.String) loadPage("index.html")}.
jtulach@836
    80
 *          {@link #locale(java.util.Locale) locale}({@link Locale#getDefault()}).
jaroslav@166
    81
 *          {@link #invoke(java.lang.String, java.lang.String[]) invoke("initialized", args)}.
jaroslav@166
    82
 *          {@link #showAndWait()};
jaroslav@166
    83
 *     System.exit(0);
jaroslav@166
    84
 * }
jaroslav@166
    85
 * </pre>
jaroslav@166
    86
 * The above will load <code>YourMain</code> class via
jaroslav@166
    87
 * a special classloader, it will locate an <code>index.html</code> (relative
jaroslav@166
    88
 * to <code>YourMain</code> class) and show it in a browser window. When the
jaroslav@166
    89
 * initialization is over, a <b>public static</b> method <em>initialized</em>
jaroslav@166
    90
 * in <code>YourMain</code> will be called with provided string parameters.
jaroslav@166
    91
 * <p>
jaroslav@166
    92
 * This module provides only API for building browsers. To use it properly one
jaroslav@166
    93
 * also needs an implementation on the classpath of one's application. For example
jaroslav@166
    94
 * use: <pre>
jaroslav@166
    95
 * &lt;dependency&gt;
jaroslav@361
    96
 *   &lt;groupId&gt;org.netbeans.html&lt;/groupId&gt;
jaroslav@361
    97
 *   &lt;artifactId&gt;net.java.html.boot.fx&lt;/artifactId&gt;
jaroslav@166
    98
 *   &lt;scope&gt;runtime&lt;/scope&gt;
jaroslav@166
    99
 * &lt;/dependency&gt;
jaroslav@166
   100
 * </pre>
jaroslav@123
   101
 *
jtulach@790
   102
 * @author Jaroslav Tulach
jaroslav@123
   103
 */
jaroslav@123
   104
public final class BrowserBuilder {
jaroslav@156
   105
    private static final Logger LOG = Logger.getLogger(BrowserBuilder.class.getName());
jaroslav@156
   106
    
jaroslav@127
   107
    private String resource;
jaroslav@127
   108
    private Class<?> clazz;
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)}.
jtulach@886
   123
     * Since introduction of {@link Id technology identifiers} the 
jtulach@886
   124
     * provided <code>context</code> objects are also passed to the 
jtulach@886
   125
     * {@link BrwsrCtx context} when it is being 
jtulach@886
   126
     * {@link Contexts#newBuilder(java.lang.Object...) created}
jtulach@886
   127
     * and can influence the selection
jtulach@886
   128
     * of available technologies 
jtulach@886
   129
     * (like {@link org.netbeans.html.json.spi.Technology},
jtulach@886
   130
     * {@link org.netbeans.html.json.spi.Transfer} or
jtulach@886
   131
     * {@link org.netbeans.html.json.spi.WSTransfer}) by name.
jaroslav@166
   132
     * 
jaroslav@288
   133
     * @param context any instances that should be available to the builder -
jaroslav@458
   134
     *   implementation dependant
jaroslav@166
   135
     * @return new browser builder
jaroslav@166
   136
     */
jaroslav@288
   137
    public static BrowserBuilder newBrowser(Object... context) {
jaroslav@288
   138
        return new BrowserBuilder(context);
jaroslav@123
   139
    }
jaroslav@123
   140
    
jaroslav@166
   141
    /** The class to load when the browser is initialized. This class
jaroslav@166
   142
     * is loaded by a special classloader (that supports {@link JavaScriptBody}
jaroslav@166
   143
     * and co.). 
jaroslav@166
   144
     * 
jaroslav@166
   145
     * @param mainClass the class to load and resolve when the browser is ready
jaroslav@166
   146
     * @return this builder
jaroslav@166
   147
     */
jaroslav@127
   148
    public BrowserBuilder loadClass(Class<?> mainClass) {
jaroslav@127
   149
        this.clazz = mainClass;
jaroslav@123
   150
        return this;
jaroslav@123
   151
    }
jtulach@656
   152
    
jtulach@656
   153
    /** Allows one to specify a runnable that should be invoked when a load
jtulach@656
   154
     * of a page is finished. This method may be used in addition or instead
jtulach@656
   155
     * of {@link #loadClass(java.lang.Class)} and 
jtulach@656
   156
     * {@link #invoke(java.lang.String, java.lang.String...)} methods.
jtulach@656
   157
     * 
jtulach@656
   158
     * @param r the code to run when the page is loaded
jtulach@656
   159
     * @return this builder
jtulach@656
   160
     * @since 0.8.1
jtulach@656
   161
     */
jtulach@656
   162
    public BrowserBuilder loadFinished(Runnable r) {
jtulach@656
   163
        this.onLoad = r;
jtulach@656
   164
        return this;
jtulach@656
   165
    }
jaroslav@156
   166
jaroslav@166
   167
    /** Page to load into the browser. If the <code>page</code> represents
jaroslav@166
   168
     * a {@link URL} known to the Java system, the URL is passed to the browser. 
jaroslav@202
   169
     * If system property <code>browser.rootdir</code> is specified, then a
jaroslav@202
   170
     * file <code>page</code> relative to this directory is used as the URL.
jaroslav@202
   171
     * If no such file exists, the system seeks for the 
jaroslav@202
   172
     * resource via {@link Class#getResource(java.lang.String)}
jaroslav@202
   173
     * method (relative to the {@link #loadClass(java.lang.Class) specified class}). 
jaroslav@202
   174
     * If such resource is not found, a file relative to the location JAR
jaroslav@202
   175
     * that contains the {@link #loadClass(java.lang.Class) main class} is 
jaroslav@202
   176
     * searched for.
jtulach@834
   177
     * <p>
jtulach@834
   178
     * The search honors provided {@link #locale}, if specified.
jtulach@834
   179
     * E.g. it will prefer <code>index_cs.html</code> over <code>index.html</code>
jtulach@834
   180
     * if the locale is set to <code>cs_CZ</code>.
jaroslav@166
   181
     * 
jaroslav@166
   182
     * @param page the location (relative, absolute, or URL) of a page to load
jtulach@834
   183
     * @return this builder
jaroslav@166
   184
     */
jaroslav@166
   185
    public BrowserBuilder loadPage(String page) {
jaroslav@166
   186
        this.resource = page;
jaroslav@166
   187
        return this;
jaroslav@166
   188
    }
jaroslav@166
   189
    
jtulach@834
   190
    /** Locale to use when searching for an initial {@link #loadPage(java.lang.String) page to load}.
jtulach@834
   191
     * Localization is best done by providing different versions of the 
jtulach@834
   192
     * initial page with appropriate suffixes (like <code>index_cs.html</code>).
jtulach@834
   193
     * Then one can call this method with value of {@link Locale#getDefault()}
jtulach@834
   194
     * to instruct the builder to use the user's current locale.
jtulach@834
   195
     * 
jtulach@834
   196
     * @param locale the locale to use or <code>null</code> if no suffix search should be performed
jtulach@834
   197
     * @return this builder
jtulach@834
   198
     * @since 1.0
jtulach@834
   199
     */
jtulach@834
   200
    public BrowserBuilder locale(Locale locale) {
jtulach@834
   201
        this.locale = locale;
jtulach@834
   202
        return this;
jtulach@834
   203
    }
jtulach@834
   204
    
jaroslav@166
   205
    /** Specifies callback method to notify the application that the browser is ready.
jaroslav@166
   206
     * There should be a <b>public static</b> method in the class specified
jaroslav@166
   207
     * by {@link #loadClass(java.lang.Class)} which takes an array of {@link String}
jaroslav@166
   208
     * argument. The method is called on the browser dispatch thread one
jaroslav@166
   209
     * the browser finishes loading of the {@link #loadPage(java.lang.String) HTML page}.
jaroslav@166
   210
     * 
jaroslav@166
   211
     * @param methodName name of a method to seek for
jaroslav@166
   212
     * @param args parameters to pass to the method
jaroslav@166
   213
     * @return this builder
jaroslav@166
   214
     */
jaroslav@156
   215
    public BrowserBuilder invoke(String methodName, String... args) {
jaroslav@156
   216
        this.methodName = methodName;
jaroslav@156
   217
        this.methodArgs = args;
jaroslav@156
   218
        return this;
jaroslav@156
   219
    }
jaroslav@166
   220
jtulach@771
   221
    /** Loader to use when searching for classes to initialize. 
jtulach@882
   222
     * If specified, this loader is going to be used to load {@link Presenter}
jtulach@838
   223
     * and {@link Contexts#fillInByProviders(java.lang.Class, org.netbeans.html.context.spi.Contexts.Builder) fill} {@link BrwsrCtx} in.
jtulach@771
   224
     * Specifying special classloader may be useful in modular systems, 
jtulach@771
   225
     * like OSGi, where one needs to load classes from many otherwise independent
jtulach@771
   226
     * modules.
jtulach@771
   227
     * 
jtulach@771
   228
     * @param l the loader to use (or <code>null</code>)
jtulach@771
   229
     * @return this builder
jtulach@771
   230
     * @since 0.9
jtulach@771
   231
     */
jtulach@771
   232
    public BrowserBuilder classloader(ClassLoader l) {
jtulach@771
   233
        this.loader = l;
jtulach@771
   234
        return this;
jtulach@771
   235
    }
jtulach@771
   236
jaroslav@166
   237
    /** Shows the browser, loads specified page in and executes the 
jaroslav@166
   238
     * {@link #invoke(java.lang.String, java.lang.String[]) initialization method}.
jaroslav@166
   239
     * The method returns when the browser is closed.
jaroslav@166
   240
     * 
jaroslav@166
   241
     * @throws NullPointerException if some of essential parameters (like {@link #loadPage(java.lang.String) page} or
jaroslav@166
   242
     *    {@link #loadClass(java.lang.Class) class} have not been specified
jaroslav@166
   243
     */
jaroslav@123
   244
    public void showAndWait() {
jaroslav@146
   245
        if (resource == null) {
jaroslav@166
   246
            throw new NullPointerException("Need to specify resource via loadPage method");
jaroslav@146
   247
        }
jaroslav@146
   248
        
jtulach@656
   249
        final Class<?> myCls;
jtulach@656
   250
        if (clazz != null) {
jtulach@656
   251
            myCls = clazz;
jtulach@656
   252
        } else if (onLoad != null) {
jtulach@656
   253
            myCls = onLoad.getClass();
jtulach@656
   254
        } else {
jtulach@656
   255
            throw new NullPointerException("loadClass, neither loadFinished was called!");
jtulach@656
   256
        }
jtulach@834
   257
        IOException mal[] = { null };
jtulach@834
   258
        URL url = findLocalizedResourceURL(resource, locale, mal, myCls);
jaroslav@288
   259
        
jaroslav@288
   260
        Fn.Presenter dfnr = null;
jaroslav@288
   261
        for (Object o : context) {
jaroslav@288
   262
            if (o instanceof Fn.Presenter) {
jaroslav@288
   263
                dfnr = (Fn.Presenter)o;
jaroslav@288
   264
                break;
jaroslav@288
   265
            }
jaroslav@288
   266
        }
jaroslav@127
   267
jtulach@771
   268
        if (dfnr == null && loader != null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class, loader)) {
jtulach@771
   269
            dfnr = o;
jtulach@771
   270
            break;
jtulach@771
   271
        }
jtulach@771
   272
        
jaroslav@288
   273
        if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) {
jaroslav@288
   274
            dfnr = o;
jaroslav@288
   275
            break;
jaroslav@288
   276
        }
jaroslav@288
   277
        
jaroslav@288
   278
        if (dfnr == null) {
jaroslav@288
   279
            throw new IllegalStateException("Can't find any Fn.Presenter");
jaroslav@288
   280
        }
jaroslav@288
   281
        
jtulach@771
   282
        final ClassLoader activeLoader;
jtulach@771
   283
        if (loader != null) {
jtulach@897
   284
            final URL res = FnContext.isJavaScriptCapable(loader);
jtulach@897
   285
            if (res != null) {
jtulach@897
   286
                throw new IllegalStateException("Loader " + loader + 
jtulach@897
   287
                    " cannot resolve @JavaScriptBody, because of " + res
jtulach@897
   288
                );
jtulach@771
   289
            }
jtulach@771
   290
            activeLoader = loader;
jaroslav@509
   291
        } else {
jtulach@897
   292
            final URL res = FnContext.isJavaScriptCapable(myCls.getClassLoader());
jtulach@897
   293
            if (res == null) {
jtulach@897
   294
                activeLoader = myCls.getClassLoader();
jtulach@897
   295
            } else {
jtulach@932
   296
                FImpl impl = new FImpl(myCls.getClassLoader());
jtulach@932
   297
                activeLoader = FnContext.newLoader(res, impl, dfnr, myCls.getClassLoader().getParent());
jtulach@932
   298
                if (activeLoader == null) {
jtulach@897
   299
                    throw new IllegalStateException("Cannot find asm-5.0.jar classes!");
jtulach@897
   300
                }
jtulach@884
   301
            }
jaroslav@509
   302
        }
jtulach@736
   303
        
jtulach@736
   304
        final Fn.Presenter dP = dfnr;
jaroslav@128
   305
jaroslav@288
   306
        class OnPageLoad implements Runnable {
jaroslav@288
   307
            @Override
jaroslav@288
   308
            public void run() {
jaroslav@288
   309
                try {
jtulach@736
   310
                    final Fn.Presenter aP = Fn.activePresenter();
jtulach@736
   311
                    final Fn.Presenter currentP = aP != null ? aP : dP;
jtulach@736
   312
                    
jtulach@771
   313
                    Thread.currentThread().setContextClassLoader(activeLoader);
jtulach@1045
   314
                    final Class<?> newClazz = onLoad != null ?
jtulach@1045
   315
                        myCls :
jtulach@1045
   316
                        Class.forName(myCls.getName(), true, activeLoader);
jtulach@886
   317
                    Contexts.Builder cb = Contexts.newBuilder(context);
jtulach@736
   318
                    if (!Contexts.fillInByProviders(newClazz, cb)) {
jtulach@736
   319
                        LOG.log(Level.WARNING, "Using empty technology for {0}", newClazz);
jtulach@736
   320
                    }
jtulach@736
   321
                    if (currentP instanceof Executor) {
jtulach@736
   322
                        cb.register(Executor.class, (Executor)currentP, 1000);
jtulach@736
   323
                    }
jtulach@736
   324
                    cb.register(Fn.Presenter.class, currentP, 1000);
jtulach@736
   325
                    BrwsrCtx c = cb.build();
jtulach@736
   326
jtulach@736
   327
                    class CallInitMethod implements Runnable {
jtulach@736
   328
                        @Override
jtulach@736
   329
                        public void run() {
jtulach@736
   330
                            Throwable firstError = null;
jtulach@736
   331
                            if (onLoad != null) {
jtulach@736
   332
                                try {
jtulach@736
   333
                                    FnContext.currentPresenter(currentP);
jtulach@736
   334
                                    onLoad.run();
jtulach@736
   335
                                } catch (Throwable ex) {
jtulach@736
   336
                                    firstError = ex;
jtulach@736
   337
                                } finally {
jtulach@736
   338
                                    FnContext.currentPresenter(null);
jtulach@736
   339
                                }
jtulach@736
   340
                            }
jtulach@736
   341
                            INIT: if (methodName != null) {
jtulach@736
   342
                                if (methodArgs.length == 0) {
jtulach@736
   343
                                    try {
jtulach@736
   344
                                        Method m = newClazz.getMethod(methodName);
jtulach@736
   345
                                        FnContext.currentPresenter(currentP);
jtulach@736
   346
                                        m.invoke(null);
jtulach@736
   347
                                        firstError = null;
jtulach@736
   348
                                        break INIT;
jtulach@736
   349
                                    } catch (Throwable ex) {
jtulach@736
   350
                                        firstError = ex;
jtulach@736
   351
                                    } finally {
jtulach@736
   352
                                        FnContext.currentPresenter(null);
jtulach@736
   353
                                    }
jtulach@736
   354
                                }
jtulach@736
   355
                                try {
jtulach@736
   356
                                    Method m = newClazz.getMethod(methodName, String[].class);
jtulach@736
   357
                                    FnContext.currentPresenter(currentP);
jtulach@736
   358
                                    m.invoke(m, (Object) methodArgs);
jtulach@736
   359
                                    firstError = null;
jtulach@736
   360
                                } catch (Throwable ex) {
jtulach@736
   361
                                    LOG.log(Level.SEVERE, "Can't call " + methodName + " with args " + Arrays.toString(methodArgs), ex);
jtulach@736
   362
                                } finally {
jtulach@736
   363
                                    FnContext.currentPresenter(null);
jtulach@736
   364
                                }
jtulach@736
   365
                            }
jtulach@736
   366
                            if (firstError != null) {
jtulach@736
   367
                                LOG.log(Level.SEVERE, "Can't initialize the view", firstError);
jtulach@736
   368
                            }
jtulach@569
   369
                        }
jaroslav@288
   370
                    }
jtulach@569
   371
                    
jtulach@736
   372
                    c.execute(new CallInitMethod());
jaroslav@288
   373
                } catch (ClassNotFoundException ex) {
jtulach@656
   374
                    LOG.log(Level.SEVERE, "Can't load " + myCls.getName(), ex);
jaroslav@128
   375
                }
jaroslav@127
   376
            }
jaroslav@127
   377
        }
jaroslav@288
   378
        dfnr.displayPage(url, new OnPageLoad());
jaroslav@127
   379
    }
jaroslav@146
   380
jtulach@834
   381
    private static URL findResourceURL(String resource, String suffix, IOException[] mal, Class<?> relativeTo) {
jtulach@834
   382
        if (suffix != null) {
jtulach@834
   383
            int lastDot = resource.lastIndexOf('.');
jtulach@834
   384
            if (lastDot != -1) {
jtulach@834
   385
                resource = resource.substring(0, lastDot) + suffix + resource.substring(lastDot);
jtulach@834
   386
            } else {
jtulach@834
   387
                resource = resource + suffix;
jtulach@834
   388
            }
jtulach@834
   389
        }
jtulach@834
   390
        
jtulach@834
   391
        URL url = null;
jtulach@834
   392
        try {
jtulach@834
   393
            String baseURL = System.getProperty("browser.rootdir"); // NOI18N
jtulach@834
   394
            if (baseURL != null) {
jtulach@834
   395
                URL u = new File(baseURL, resource).toURI().toURL();
jtulach@834
   396
                if (isReal(u)) {
jtulach@834
   397
                    url = u;
jtulach@834
   398
                }
jtulach@834
   399
            } 
jtulach@834
   400
            
jtulach@834
   401
            {
jtulach@834
   402
                URL u = new URL(resource);
jtulach@834
   403
                if (suffix == null || isReal(u)) {
jtulach@834
   404
                    url = u;
jtulach@834
   405
                }
jtulach@834
   406
                return url;
jtulach@834
   407
            }
jtulach@834
   408
        } catch (MalformedURLException ex) {
jtulach@834
   409
            mal[0] = ex;
jtulach@834
   410
        }
jtulach@834
   411
        
jtulach@834
   412
        if (url == null) {
jtulach@834
   413
            url = relativeTo.getResource(resource);
jtulach@834
   414
        }
jtulach@834
   415
        if (url == null) {
jtulach@834
   416
            final ProtectionDomain pd = relativeTo.getProtectionDomain();
jtulach@834
   417
            if (pd != null && pd.getCodeSource() != null) {
jtulach@834
   418
                URL jar = pd.getCodeSource().getLocation();
jtulach@834
   419
                try {
jtulach@834
   420
                    URL u = new URL(jar, resource);
jtulach@834
   421
                    if (isReal(u)) {
jtulach@834
   422
                        url = u;
jtulach@834
   423
                    }
jtulach@834
   424
                } catch (MalformedURLException ex) {
jtulach@834
   425
                    ex.initCause(mal[0]);
jtulach@834
   426
                    mal[0] = ex;
jtulach@834
   427
                }
jtulach@834
   428
            }
jtulach@834
   429
        }
jtulach@834
   430
        if (url == null) {
jtulach@834
   431
            URL res = BrowserBuilder.class.getResource("html4j.txt");
jtulach@834
   432
            LOG.log(Level.FINE, "Found html4j {0}", res);
jtulach@834
   433
            if (res != null) {
jtulach@834
   434
                try {
jtulach@834
   435
                    URLConnection c = res.openConnection();
jtulach@834
   436
                    LOG.log(Level.FINE, "testing : {0}", c);
jtulach@834
   437
                    if (c instanceof JarURLConnection) {
jtulach@834
   438
                        JarURLConnection jc = (JarURLConnection) c;
jtulach@834
   439
                        URL base = jc.getJarFileURL();
jtulach@834
   440
                        for (int i = 0; i < 50; i++) {
jtulach@834
   441
                            URL u = new URL(base, resource);
jtulach@834
   442
                            if (isReal(u)) {
jtulach@834
   443
                                url = u;
jtulach@834
   444
                                break;
jtulach@834
   445
                            }
jtulach@834
   446
                            base = new URL(base, "..");
jtulach@834
   447
                        }
jtulach@834
   448
                    }
jtulach@834
   449
                } catch (IOException ex) {
jtulach@834
   450
                    mal[0] = ex;
jtulach@834
   451
                }
jtulach@834
   452
            }
jtulach@834
   453
        }
jtulach@834
   454
        return url;
jtulach@834
   455
    }
jtulach@834
   456
jtulach@834
   457
    static URL findLocalizedResourceURL(String resource, Locale l, IOException[] mal, Class<?> relativeTo) {
jtulach@834
   458
        URL url = null;
jtulach@834
   459
        if (l != null) {
jtulach@834
   460
            url = findResourceURL(resource, "_" + l.getLanguage() + "_" + l.getCountry(), mal, relativeTo);
jtulach@834
   461
            if (url != null) {
jtulach@834
   462
                return url;
jtulach@834
   463
            }
jtulach@834
   464
            url = findResourceURL(resource, "_" + l.getLanguage(), mal, relativeTo);
jtulach@834
   465
        }
jtulach@834
   466
        if (url != null) {
jtulach@834
   467
            return url;
jtulach@834
   468
        }
jtulach@834
   469
        return findResourceURL(resource, null, mal, relativeTo);
jtulach@834
   470
    }
jtulach@834
   471
    
jtulach@834
   472
    private static boolean isReal(URL u) {
jtulach@834
   473
        try {
jtulach@834
   474
            URLConnection conn = u.openConnection();
jtulach@834
   475
            if (conn instanceof HttpURLConnection) {
jtulach@834
   476
                HttpURLConnection hc = (HttpURLConnection) conn;
jtulach@834
   477
                hc.setReadTimeout(5000);
jtulach@834
   478
                if (hc.getResponseCode() >= 300) {
jtulach@834
   479
                    throw new IOException("Wrong code: " + hc.getResponseCode());
jtulach@834
   480
                }
jtulach@834
   481
            }
jtulach@834
   482
            InputStream is = conn.getInputStream();
jtulach@834
   483
            is.close();
jtulach@834
   484
            LOG.log(Level.FINE, "found real url: {0}", u);
jtulach@834
   485
            return true;
jtulach@834
   486
        } catch (IOException ignore) {
jtulach@834
   487
            LOG.log(Level.FINE, "Cannot open " + u, ignore);
jtulach@834
   488
            return false;
jtulach@834
   489
        }
jtulach@834
   490
    }
jtulach@834
   491
jaroslav@128
   492
    private static final class FImpl implements FindResources {
jaroslav@128
   493
        final ClassLoader l;
jaroslav@127
   494
jaroslav@127
   495
        public FImpl(ClassLoader l) {
jaroslav@127
   496
            this.l = l;
jaroslav@127
   497
        }
jaroslav@127
   498
jaroslav@127
   499
        @Override
jaroslav@127
   500
        public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
jaroslav@127
   501
            if (oneIsEnough) {
jaroslav@127
   502
                URL u = l.getResource(path);
jaroslav@127
   503
                if (u != null) {
jaroslav@127
   504
                    results.add(u);
jaroslav@127
   505
                }
jaroslav@127
   506
            } else {
jaroslav@127
   507
                try {
jaroslav@127
   508
                    Enumeration<URL> en = l.getResources(path);
jaroslav@127
   509
                    while (en.hasMoreElements()) {
jaroslav@127
   510
                        results.add(en.nextElement());
jaroslav@127
   511
                    }
jaroslav@127
   512
                } catch (IOException ex) {
jaroslav@127
   513
                    // no results
jaroslav@127
   514
                }
jaroslav@127
   515
            }
jaroslav@127
   516
        }
jaroslav@127
   517
        
jaroslav@123
   518
    }
jaroslav@123
   519
}