blob: a0cea41e6dd32c7e998ea7d3b979a89bdd07879f [file] [log] [blame]
/**
* HTML via Java(tm) Language Bindings
* Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. apidesign.org
* designates this particular file as subject to the
* "Classpath" exception as provided by apidesign.org
* in the License file that accompanied this code.
*
* You should have received a copy of the GNU General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
*/
package org.apidesign.html.boot.spi;
import java.io.Closeable;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import net.java.html.js.JavaScriptBody;
import org.apidesign.html.boot.impl.FnContext;
/** Represents single JavaScript function that can be invoked.
* Created via {@link Presenter#defineFn(java.lang.String, java.lang.String...)}.
*
* @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
*/
public abstract class Fn {
private final Presenter presenter;
/**
* @deprecated Ineffective as of 0.6.
* Provide a presenter via {@link #Fn(org.apidesign.html.boot.spi.Fn.Presenter)}
* constructor
*/
@Deprecated
protected Fn() {
this(null);
}
/** Creates new function object and associates it with given presenter.
*
* @param presenter the browser presenter associated with this function
* @since 0.6
*/
protected Fn(Presenter presenter) {
this.presenter = presenter;
}
/** True, if currently active presenter is the same as presenter this
* function has been created for via {@link #Fn(org.apidesign.html.boot.spi.Fn.Presenter)}.
*
* @return true, if proper presenter is used
*/
public final boolean isValid() {
return FnContext.currentPresenter() == presenter;
}
/** Helper method to check if the provided instance is valid function.
* Checks if the parameter is non-null and if so, does {@link #isValid()}
* check.
*
* @param fnOrNull function or <code>null</code>
* @return true if the parameter is non-null and valid
* @since 0.7
*/
public static boolean isValid(Fn fnOrNull) {
return fnOrNull != null && fnOrNull.isValid();
}
/** Helper method to find current presenter and ask it to define new
* function by calling {@link Presenter#defineFn(java.lang.String, java.lang.String...)}.
*
* @param caller the class who wishes to define the function
* @param code the body of the function (can reference <code>this</code> and <code>names</code> variables)
* @param names names of individual parameters
* @return the function object that can be {@link Fn#invoke(java.lang.Object, java.lang.Object...) invoked}
* @since 0.7
*/
public static Fn define(Class<?> caller, String code, String... names) {
return FnContext.currentPresenter().defineFn(code, names);
}
private static final Map<String,Set<Presenter>> LOADED = new HashMap<String, Set<Presenter>>();
public static Fn preload(final Fn fn, final Class<?> caller, final String resource) {
return new Fn() {
@Override
public Object invoke(Object thiz, Object... args) throws Exception {
final Presenter p = FnContext.currentPresenter();
Set<Presenter> there = LOADED.get(resource);
if (there == null) {
there = new HashSet<Presenter>();
LOADED.put(resource, there);
}
if (there.add(p)) {
InputStream is = caller.getClassLoader().getResourceAsStream(resource);
try {
InputStreamReader r = new InputStreamReader(is, "UTF-8");
p.loadScript(r);
} finally {
is.close();
}
}
return fn.invoke(thiz, args);
}
};
}
/** The currently active presenter.
*
* @return the currently active presenter or <code>null</code>
* @since 0.7
*/
public static Presenter activePresenter() {
return FnContext.currentPresenter();
}
/** Activates given presenter. Used by the code generated by
* {@link JavaScriptBody} annotation:
* <pre>
* try ({@link Closeable} c = Fn.activate(presenter)) {
* doCallsInPresenterContext();
* }
* </pre>
*
* @param p the presenter that should be active until closable is closed
* @return the closable to close
* @since 0.7
*/
public static Closeable activate(Presenter p) {
return FnContext.activate(p);
}
/** Invokes the defined function with specified <code>this</code> and
* appropriate arguments.
*
* @param thiz the meaning of <code>this</code> inside of the JavaScript
* function - can be <code>null</code>
* @param args arguments for the function
* @return return value from the function
* @throws Exception if something goes wrong, as exception may be thrown
*/
public abstract Object invoke(Object thiz, Object... args) throws Exception;
/** The representation of a <em>presenter</em> - usually a browser window.
* Should be provided by a library included in the application and registered
* in <code>META-INF/services</code>, for example with
* <code>@ServiceProvider(service = Fn.Presenter.class)</code> annotation.
*/
public interface Presenter {
/** Creates new function with given parameter names and provided body.
*
* @param code the body of the function. Can refer to variables named
* as <code>names</code>
* @param names names of parameters of the function - these will be
* available when the <code>code</code> body executes
*
* @return function that can be later invoked
*/
public Fn defineFn(String code, String... names);
/** Opens the browser, loads provided page and when the
* page is ready, it calls back to the provider runnable.
*
* @param page the URL for the page to display
* @param onPageLoad callback when the page is ready
*/
public void displayPage(URL page, Runnable onPageLoad);
/** Loads a script into the browser JavaScript interpreter and
* executes it.
* @param code the script to execute
* @throws Exception if something goes wrong, throw an exception
*/
public void loadScript(Reader code) throws Exception;
}
}