package com.asayama.gwt.angular.client; import java.util.logging.Level; import java.util.logging.Logger; import com.asayama.gwt.angular.client.Injector.Inject; import com.asayama.gwt.jsni.client.Closure; import com.asayama.gwt.jsni.client.Function; import com.asayama.gwt.jsni.client.JSArray; import com.asayama.gwt.jsni.client.JSClosure; import com.asayama.gwt.jsni.client.JSFunction; import com.asayama.gwt.util.client.Strings; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; /** * Provides an abstract implementation for AngularJS's Module object, and acts * as an adapter for GWT module (represented by the EntryPoint interface) and * Angular module (represented by the added interfaces in this class. * * <p> * <a href="http://docs.angularjs.org/api/angular.Module"> * http://docs.angularjs.org/api/angular.Module</a> * </p><p> * An Angular module represents a set of Angular components, such as controllers, * services, filters, and directives. In and of itself, module component does * not offer any added functionality. The task of registering various components * relevant to the module is done by implementing the <code>onModuleLoad()</code> * method. * </p><p> * It is recommended that the code to be organized such that a single GWT * module represents a single Angular module. This will help the code kept * organized, and easier to understand, though this is not strictly enforced by * this framework. More advanced users may create separate sets of GWT and * Angular module hierarchies and assemble them any way he wishes. * </p><p> * If you choose to mirror the GWT and Angular modules, then be sure to register * your implementation of your Module to the GWT module descriptor as an * entry point, e.g. com/example/app/MyModule.gwt.xml * <pre>{@code * <?xml version="1.0" encoding="UTF-8"?> * <module> * <inherits name="com.google.gwt.user.User" /> * <inherits name="com.asayama.gwt.angular.NG" /> * <source path="client" /> * <entry-point class="com.example.app.client.MyModule" /> * </module> * }</pre> * * The corresponding MyModule class definition may look like * <pre> * // In this hypothetical example, the module consists of a constant named * // "pages", directive named "myHello", filter named "reverse", and two * // controllers, "com.exmaple.client.FooController" and * // "com.example.client.BarController". * * package com.example.app.client; * * public class MyModule extends AbstractModule implements EntryPoint { * public void onModuleLoad() { * Angular.module(this); * constant("pages", JSArray.create(new String[]{ * "Introduction", "Theory", "Design", "Implementation", * "Test Methods", "Test Results", "Analysis", "Reference" * })); * directive(MyHello.class); * filter(Reverse.class); * controller(FooController.class); * controller(BarController.class); * } * } * </pre> * * Note that this module focuses on registering the components into a single * module, and is not concerned with building the user interface. The work of * building the user interface (or bootstrapping) should be the job of a GWT * module, and it is recommended that this is separated from the rest of the * Angular module hierarchy. For example, com/example/ui/MyEntryPoint.gwt.xml * <pre>{@code * <?xml version="1.0" encoding="UTF-8"?> * <module> * <inherits name="com.google.gwt.user.User" /> * <inherits name="com.example.app.MyModule" /> * <source path="client" /> * <entry-point class="com.example.ui.client.MyEntryPoint" /> * </module> * }</pre> * * The corresponding MyEntryPoint class definition may look like * <pre> * package com.example.ui.client; * * public class MyEntryPoint implements EntryPoint { * public void onModuleLoad() { * Angular.bootstrap(); * } * } * </pre> * * If you follow this code organization convention, then the module dependency * declared with {@code <inherits>} statements in *.gwt.xml files will be * consistent with the Angular module dependency (i.e. "requires" property * in AngularJS). You should have only one entry point that bootstraps the user * interface per application. * * @author kyoken74 */ public abstract class AbstractModule { private static final String CLASS = AbstractModule.class.getName(); private static final Logger LOG = Logger.getLogger(CLASS); /** * References to a JavaScriptObject representation known to the underlying * AngularJS framework. * * @see Angular#module(AbstractModule, Closure, String...) */ protected NGModule ngo; /** * TODO Implement this method * @param klass * @return */ public <P extends Provider<?>> AbstractModule provider(Class<P> klass) { throw new UnsupportedOperationException("This method is not yet supoprted"); } /** * @deprecated Replaced by {@link #service(Class)} since 0.0.68 */ @Deprecated public <S extends Service> AbstractModule factory(Class<S> klass) { return factory(new DefaultFactory<S>(klass)); } /** * Registers a service object factory instance with the module, so that the * service object is created by the factory if/when the service is * requested. The method is useful if the service creation needs * customization/configuration, e.g. the creation of the service depends on * other resources. If you merely wish to create an object via * <code>new</code> or <code>GWT.create()</code>, then there is a * convenience method {@link AbstractModule#service(Class) } so you do not * have to define or instantiate a custom factory. * * @param factory An instance of Factory object that creates a Service object. */ public <S extends Service> AbstractModule factory(final Factory<S> factory) { String name = factory.getServiceClass().getName(); return factory(name, factory); } protected <S extends Service> AbstractModule factory(final String name, final Factory<S> factory) { Function<Service> initializer = new Function<Service>() { @Override public Service call(Object... args) { String m = ""; try { final String SNAME = factory.getServiceClass().getName(); LOG.finest(m = "calling onFactoryLoad() for " + SNAME); factory.onFactoryLoad(); m = "calling create() for " + SNAME; Service service = factory.create(); m = "binding dependency to " + SNAME; ServiceBinderFactory binderFactory = GWT.create(ServiceBinderFactory.class); JSClosure binder = binderFactory.create(service); if (binder != null) { binder.apply(args); } return service; } catch (Exception e) { LOG.log(Level.WARNING, "Exception while " + m, e); return null; } } }; ServiceDependencyInspector inspector = GWT.create(ServiceDependencyInspector.class); String[] dependencies = inspector.inspect(factory.getServiceClass()); ngo.factory(name, JSArray.create(dependencies), JSFunction.create(initializer)); return this; } /** * Registers a Service class to the module. This method is equivalent to * <pre> * MyModule module = new MyModule(); * {@code module.factory(new DefaultFactory<S>(MyService.class)); } * </pre> */ public <S extends Service> AbstractModule service(Class<S> klass) { return factory(new DefaultFactory<S>(klass)); } /** * Registers an instance of a Service object to the module. This method is * useful if the service object has already been instantiated by someone * else outside of Angular, so there is no benefit to deferring the * instantiation of that object. * <p> * If you have defined your own Service type, and wish to let the framework * handle the instantiation, then use {@link #service(Class)} instead. * </p> * * @see Factory * @see DefaultFactory */ public <S extends Service> AbstractModule service(S service) { // TODO Auto-generated method stub throw new UnsupportedOperationException("This method is not yet implemented"); } /** * Registers an object/data structure to the module. Objects registered by * this method are accessible by services and controllers via {@link Inject} * annotation within the same module. For example, * <pre> * MyModule module = new MyModule(); * Angular.module(module); * module.value("myValue", "Hello, World!"); * module.service(MyService.class); * ... * class MyService implements Service { * {@code @Injector.Inject("myValue") } * String myValue; //injector assigns "Hello, World!" * } * </pre> * The concept of value in AngularJS is a useful way to manage objects * that are of module scope. With GWT, however, we can accomplish the same * thing by simply using the Java package name with {@code public static}, * if the value is used exclusively in GWT. The method is nevertheless * useful if your module is expected to be a hybrid of GWT and JavaScript. * * @param name Name of the object. * @param object The instance of the object. */ public AbstractModule value(String name, Object object) { ngo.value(name, object); return this; } /** * Registers an object/data structure to the module. Objects registered by * this method are accessible by services and controllers via {@link Inject} * annotation within the same module, or to a module during configuration. * For example, * <pre> * MyModule module = new MyModule(); * Angular.module(module); * module.constant("myConstant", "Hello, World!"); * {@code module.configure(MyServiceProvider.class, Configurator<MyServiceProvider>()} { * public void configure(MyServiceProvider provider) { * //configure provider * } * }); * ... * class MyServiceProvider implements Provider { * {@code @Injector.Inject("myConstant") } * String myConstant; //injector assigns "Hello, World!" * } * </pre> * The concept of value in AngularJS is a useful way to manage objects * that are of module scope. With GWT, however, we can accomplish the same * thing by simply using the Java package name with {@code public static}, * if the value is used exclusively in GWT. The method is nevertheless * useful if your module is expected to be a hybrid of GWT and JavaScript. * * @param name Name of the object. * @param object The instance of the object. */ public AbstractModule constant(String name, Object value) { ngo.constant(name, value); return this; } // TODO Implement this method. //public <A extends Animation> Module animation(Factory<A>) /** * Registers a Filter type with the module. The name of the component is * derived from the class name, e.g. * <pre> * // Derived name is "myFilter". Java package is omitted. * filter(com.example.MyFilter.class); * </pre> * * @param klass Filter type */ public <F extends Filter> AbstractModule filter(Class<F> klass) { String className = Strings.simpleName(klass); String name = Strings.decapitalize(className); JSFunction<NGFilter> filterFactory = JSFunction.create(new DefaultFilterFactory<F>(name, klass)); FilterDependencyInspector inspector = GWT.create(FilterDependencyInspector.class); JSArray<String> dependencies = JSArray.create(inspector.inspect(klass)); ngo.filter(name, dependencies, filterFactory); return this; } /** * Registers a controller component with the module. The name of the * component is derived from the class name, e.g. * <pre> * // Derived name is "com.example.MyController". * controller(com.example.MyController.class); * </pre> * * @param klass Controller type. */ public <C extends Controller> AbstractModule controller(Class<C> klass) { String name = klass.getName(); JSClosure constructor = JSClosure.create(new DefaultControllerConstructor<C>(name, klass)); ControllerDependencyInspector inspector = GWT.create(ControllerDependencyInspector.class); JSArray<String> dependencies = JSArray.create(inspector.inspect(klass)); dependencies.unshift("$scope"); ngo.controller(name, dependencies, constructor); return this; } /** * Registers a Directive type with the module. The name of the component is * derived from the class name, e.g. * <pre> * // Derived name is "myDirective". Java package is omitted. * // Attribute directive usage is {@code <div data-my-directive> } * // Class directive usage is {@code <div class="my-directive"> } * // Element directive usage is {@code <my-directive> } * directive(com.example.MyDirective.class); * </pre> * * @param Directive type. */ public <D extends Directive> AbstractModule directive(Class<D> klass) { String className = Strings.simpleName(klass); String name = Strings.decapitalize(className); JSFunction<NGDirective> directiveFactory = JSFunction.create(new DefaultDirectiveFactory<D>(name, klass)); DirectiveDependencyInspector inspector = GWT.create(DirectiveDependencyInspector.class); JSArray<String> dependencies = JSArray.create(inspector.inspect(klass)); ngo.directive(name, dependencies, directiveFactory); return this; } /** * Configures a provider prior to obtaining the factory from it. * Use the {@link Configurator} to configure the provider injected into * the module. * * @param klass Provider type to be configured at module initialization. * @param configuration Configures the provider. */ public <P extends Provider<?>> AbstractModule config(final Class<P> klass, final Configurator<P> configurator) { Function<P> initializer = new Function<P>() { @Override public P call(Object... args) { String m = ""; try { ProviderCreator creator = GWT.create(ProviderCreator.class); P provider = creator.create(klass); ProviderBinderFactory binderFactory = GWT.create(ProviderBinderFactory.class); JSClosure binder = binderFactory.create(provider); if (binder != null) { binder.apply(args); } LOG.log(Level.FINEST, "configuring " + klass.getName()); configurator.configure(provider); return provider; } catch (Exception e) { LOG.log(Level.WARNING, "Exception while " + m, e); return null; } } }; ProviderDependencyInspector inspector = GWT.create(ProviderDependencyInspector.class); String[] dependencies = inspector.inspect(klass); ngo.config(JSArray.create(dependencies), JSFunction.create(initializer)); return this; } /** * Runs module initialization task provided by <code>klass</code>. * Whilst the task is defined as a <code>Runnable</code>, the task is not * executed in a separate thread. The tasks are executed synchronously. * * @param runnable Module initialization task. * @since 0.1.1 */ public <R extends Runnable> AbstractModule run(final Class<R> klass) { Closure initializer = new Closure() { @Override public void exec(Object... args) { String m = "exec(Object...)"; try { m = "creating task " + klass.getName(); RunnableCreator creator = GWT.create(RunnableCreator.class); R runnable = creator.create(klass); m = "creating binder for task " + klass.getName(); RunnableBinderFactory binderFactory = GWT.create(RunnableBinderFactory.class); JSClosure binder = binderFactory.create(runnable); if (binder != null) { m = "injecting dependencies into task " + klass.getName(); binder.apply(args); } m = "running task " + klass.getName(); runnable.run(); } catch (Exception e) { LOG.log(Level.WARNING, "Exception while " + m, e); } } }; RunnableDependencyInspector inspector = GWT.create(RunnableDependencyInspector.class); String[] dependencies = inspector.inspect(klass); ngo.run(JSArray.create(dependencies), JSClosure.create(initializer)); return this; } // Properties /** * Returns the name of the module. Every Angular module must be registered * with Angular framework, and give a name at the time of the registration. * This ensures that only one instance of the module per name exists at * runtime. See {@link Angular } class methods for more details. */ public String getName() { return ngo.getName(); } } class NGModule extends JavaScriptObject { protected NGModule() { } // Methods /** * @param name service name * @param dependencies * @param providerType Construction function for creating new instance of the service. */ final native <S extends Service> NGModule provider(String name, JSArray<String> dependencies, JSFunction<S> providerType) /*-{ dependencies.push(providerType); return this.provider(name, dependencies); }-*/; /** * @param name service name * @param dependencies * @param providerFunction Function for creating new instance of the service. */ final native <S extends Service> NGModule factory(String name, JSArray<String> dependencies, JSFunction<S> providerFunction) /*-{ dependencies.push(providerFunction); return this.factory(name, dependencies); }-*/; /** * @param name service name * @param dependencies * @param constructor A constructor function that will be instantiated. */ final native <S extends Service> NGModule service(String name, JSArray<String> dependencies, JSFunction<S> constructor) /*-{ dependencies.push(constructor); return this.factory(name, dependencies); }-*/; /** * Registers a value object as a service to the module. * * @param name service name * @param object Service instance object. */ final native NGModule value(String name, Object object) /*-{ return this.value(name, object); }-*/; /** * Registers a value object as a service to the module. * * @param name constant name * @param object Constant value. */ final native NGModule constant(String name, Object object) /*-{ return this.constant(name, object); }-*/; /** * TODO Implement this AbstractModule.animation() * <p> * Depends on "ngAnimate" module. * </p> * * @param name animation name * @param animationFactory Factory function for creating new instance of an animation. */ final native NGModule animation(String name, JSFunction<?> animationFactory) /*-{ return this; }-*/; /** * @param name Filter name. * @param dependencies Dependency annotation * @param filterFactory Factory function for creating new instance of filter. */ final native NGModule filter(String name, JSArray<String> dependencies, JSFunction<NGFilter> filterFactory) /*-{ dependencies.push(filterFactory); return this.filter(name, dependencies); }-*/; /** * @param name Controller name. * @param dependencies Dependency annotation * @param constructor Controller constructor function. */ final native NGModule controller(String name, JSArray<String> dependencies, JSClosure constructor) /*-{ dependencies.push(constructor); return this.controller(name, dependencies); }-*/; /** * @param name Directive name. * @param dependencies Dependency annotation * @param directiveFactory Factory function for creating new instance of directives. */ final native NGModule directive(String name, JSArray<String> dependencies, JSFunction<NGDirective> directiveFactory) /*-{ dependencies.push(directiveFactory); return this.directive(name, dependencies); }-*/; /** * @param dependencies Dependency annotation * @param configFn Execute this function on module load. Useful for service configuration. */ final native NGModule config(JSArray<String> dependencies, JavaScriptObject configFn) /*-{ dependencies.push(configFn); return this.config(dependencies); }-*/; /** * @param dependencies Dependency annotation * @param initializationFn Execute this function after injector creation. Useful for application initialization. */ final native NGModule run(JSArray<String> dependencies, JavaScriptObject initializationFn) /*-{ dependencies.push(initializationFn); return this.run(dependencies); }-*/; // Properties /** * @return the list of modules which the injector will load before the current module is loaded. */ final native JSArray<String> getRequires() /*-{ return this.requires; }-*/; /** * @return Name of the module. */ final native String getName() /*-{ return this.name; }-*/; }