nz.co.doltech.gwt.sdm.SuperDevCompiler.java Source code

Java tutorial

Introduction

Here is the source code for nz.co.doltech.gwt.sdm.SuperDevCompiler.java

Source

/**
 * Copyright 2015 Doltech Systems Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package nz.co.doltech.gwt.sdm;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.HeadElement;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.ScriptElement;

import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

public class SuperDevCompiler {
    private static final Logger logger = Logger.getLogger(SuperDevCompiler.class.getName());

    public static final String CODE_SERVER_URL = "http://127.0.0.1:9876";

    private static SuperDevCompiler superDevCompiler = null;

    public static SuperDevCompiler get() {
        return get(CODE_SERVER_URL);
    }

    public static SuperDevCompiler get(String serverUrl) {
        if (superDevCompiler == null) {
            superDevCompiler = new SuperDevCompiler(GWT.getModuleName(), serverUrl);
        }
        return superDevCompiler;
    }

    public interface InjectedCallback {
        void onInjected();
    }

    public interface StartedCallback {
        void onStarted(String moduleName, String requestUrl);
    }

    public interface PollCallback {
        void onPoll(float startTime);
    }

    public interface CompletedCallback {
        /**
         * Called when the CodeServer successfully compiles.
         * @return true to stop the automatic page refresh.
         */
        boolean onCompleted(JavaScriptObject json);
    }

    public interface FailedCallback {
        void onFailed(String reason, String logUrl);
    }

    private String moduleName;
    private String serverUrl;
    private boolean injected = false;

    private Set<InjectedCallback> injectedCallbacks = new HashSet<>();
    private Set<StartedCallback> startCallbacks = new HashSet<>();
    private Set<PollCallback> pollCallbacks = new HashSet<>();
    private Set<CompletedCallback> completeCallbacks = new HashSet<>();
    private Set<FailedCallback> failedCallbacks = new HashSet<>();

    private SuperDevCompiler(String moduleName, String serverUrl) {
        this.moduleName = moduleName;
        this.serverUrl = serverUrl;

        // Setup __gwt_bookmarklet_globals
        setupGlobals();

        // Inject the dev_mode_on.js
        injectDevModeOn();
    }

    public String getModuleName() {
        return moduleName;
    }

    public String getServerUrl() {
        return serverUrl;
    }

    private native void setupGlobals() /*-{
                                       // Setup globals
                                       var globals = $wnd.__gwt_bookmarklet_globals;
                                       if (typeof globals == 'undefined') {
                                       globals = {
                                       callback_counter: 0,
                                       callbacks: {}
                                       };
                                       $wnd.__gwt_bookmarklet_globals = globals;
                                       }
                                       }-*/;

    /**
     * Invoke a SuperDevMode compilation with auto refresh.
     */
    public void compile() {
        compile(true);
    }

    /**
     * Invoke a SuperDevMode compilation.
     * @param refresh if true will auto refresh module.
     */
    public native void compile(boolean refresh) /*-{
                                                if (!!$wnd.__gwt_bookmarklet_globals.compiling) {
                                                // A module is already being compiled.
                                                return;
                                                }
                                                    
                                                var moduleName = this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::moduleName;
                                                var serverUrl = this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::serverUrl;
                                                    
                                                // Compile erorr checking
                                                var error = null;
                                                if(moduleName) {
                                                error = this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::getCannotCompileError()();
                                                }
                                                var logUrl = serverUrl + '/log/' + moduleName;
                                                    
                                                if (moduleName && !error) {
                                                // Probably a regular compile, so check in the page.
                                                var active_modules = $wnd.__gwt_activeModules;
                                                getPropMap = active_modules[moduleName].bindings;
                                                    
                                                var params = this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::getBindingParameters(Lcom/google/gwt/core/client/JavaScriptObject;)(getPropMap);
                                                var urlPrefix = serverUrl + "/recompile/" + moduleName + "?" + params;
                                                    
                                                var _this = this;
                                                var lastPollStart;
                                                    
                                                function onPollFinished(event) {
                                                if ($wnd.__gwt_bookmarklet_globals.compiling && event.status == "compiling") {
                                                //dialog.updateProgress(event);
                                                // Date.now() fails in IE8
                                                var waitTime = 1000 - (new Date().getTime() - lastPollStart);
                                                if (waitTime > 0) {
                                                setTimeout(poll, waitTime);
                                                } else {
                                                poll();
                                                }
                                                }
                                                // otherwise it's idle or an unknown event type, so stop
                                                }
                                                    
                                                function poll() {
                                                if ($wnd.__gwt_bookmarklet_globals.compiling) {
                                                // Date.now() fails in IE8
                                                lastPollStart = new Date().getTime();
                                                _this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::onPoll(F)(lastPollStart);
                                                _this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::callJsonp(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(serverUrl + '/progress?', onPollFinished);
                                                }
                                                }
                                                    
                                                function onCompileCompleted(json) {
                                                $wnd.__gwt_bookmarklet_globals.compiling = false;
                                                if (json.status != 'ok') {
                                                _this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::onCompileFailed(Ljava/lang/String;)(logUrl);
                                                return;
                                                }
                                                if(!_this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::onCompileCompleted(Lcom/google/gwt/core/client/JavaScriptObject;)(json)) {
                                                _this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::reloadInDevMode()();
                                                }
                                                }
                                                    
                                                $wnd.__gwt_bookmarklet_globals.compiling = true;
                                                    
                                                setTimeout(poll, 1000);
                                                this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::onCompileStarted(Ljava/lang/String;Ljava/lang/String;)(moduleName, urlPrefix);
                                                this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::callJsonp(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(urlPrefix, onCompileCompleted);
                                                }
                                                else {
                                                this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::onCompileFailed(Ljava/lang/String;Ljava/lang/String;)(error, logUrl)
                                                }
                                                }-*/;

    private void onCompileStarted(String moduleName, String requestUrl) {
        for (StartedCallback callback : startCallbacks) {
            if (callback != null) {
                callback.onStarted(moduleName, requestUrl);
            }
        }
    }

    private void onPoll(float startTime) {
        for (PollCallback callback : pollCallbacks) {
            if (callback != null) {
                callback.onPoll(startTime);
            }
        }
    }

    private boolean onCompileCompleted(JavaScriptObject json) {
        boolean stopRefresh = false;
        for (CompletedCallback callback : completeCallbacks) {
            if (callback != null) {
                stopRefresh = stopRefresh || callback.onCompleted(json);
            }
        }
        return stopRefresh;
    }

    private void onCompileFailed(String logUrl) {
        onCompileFailed("GWT compilation failed, check the logs for the reason why.", logUrl);
    }

    private void onCompileFailed(String reason, String log) {
        for (FailedCallback callback : failedCallbacks) {
            if (callback != null) {
                callback.onFailed(reason, log);
            }
        }
    }

    private native String getBindingParameters(JavaScriptObject getPropMap) /*-{
                                                                            var session_key = '__gwtDevModeSession:' + this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::moduleName;
                                                                                
                                                                            var prop_map = getPropMap();
                                                                            var props = [];
                                                                            for (var key in prop_map) {
                                                                            props.push(encodeURIComponent(key) + '=' + encodeURIComponent(prop_map[key]));
                                                                            }
                                                                                
                                                                            var params;
                                                                            if (!props.length) {
                                                                            // There is only one permutation, maybe because we're in dev mode already.
                                                                            // Use the cached value if present.
                                                                            var cached = window.sessionStorage[session_key];
                                                                            return cached || '';
                                                                            }
                                                                                
                                                                            var encoded = props.join('&') + '&';
                                                                            // Cache it for the next recompile.
                                                                            window.sessionStorage[session_key] = encoded;
                                                                            return encoded;
                                                                            }-*/;

    private native void callJsonp(String urlPrefix, JavaScriptObject callback) /*-{
                                                                               var callbackId = 'c' + $wnd.__gwt_bookmarklet_globals.callback_counter++;
                                                                               $wnd.__gwt_bookmarklet_globals.callbacks[callbackId] = function(json) {
                                                                               delete $wnd.__gwt_bookmarklet_globals.callbacks[callbackId];
                                                                               callback(json);
                                                                               };
                                                                                   
                                                                               var url = urlPrefix + '_callback=__gwt_bookmarklet_globals.callbacks.' +
                                                                               callbackId;
                                                                                   
                                                                               var script = $doc.createElement('script');
                                                                               script.src = url;
                                                                               $doc.getElementsByTagName('head')[0].appendChild(script);
                                                                               }-*/;

    /**
     * Determines if the code server is configured to run the given module.
     * @return {boolean} true if the code server supports the given module.
     */
    private native boolean isModuleOnCodeServer() /*-{
                                                  try {
                                                  var modules_on_codeserver = $wnd.__gwt_codeserver_config.moduleNames;
                                                  // Support browsers without indexOf() (e.g. IE8).
                                                  for (var i = 0; i < modules_on_codeserver.length; i++) {
                                                  if (modules_on_codeserver[i] == this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::moduleName) {
                                                  return true;
                                                  }
                                                  }
                                                  } catch(e) {}
                                                  return false;
                                                      
                                                  }-*/;

    /**
     * Determines whether we can recompile a module and see the results. If not,
     * explains why not.
     * @return {string} The error message, or null if there is no error and
     *     a recompile will succeed.
     */
    private native String getCannotCompileError() /*-{
                                                  if (!this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::isModuleOnCodeServer()()) {
                                                  return 'The code server isn\'t configured to compile this module.';
                                                  }
                                                  var moduleName = this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::moduleName;
                                                      
                                                  var modules_on_page = $wnd.__gwt_activeModules;
                                                  if (!modules_on_page || !(moduleName in modules_on_page)) {
                                                  return 'The current page doesn\'t have this module.';
                                                  }
                                                      
                                                  var mod = modules_on_page[moduleName];
                                                      
                                                  var dev_mode_key = '__gwtDevModeHook:' + moduleName;
                                                  var dev_mode_on = mod['superdevmode'] ||
                                                  window.sessionStorage[dev_mode_key];
                                                      
                                                  if (!dev_mode_on && !mod.canRedirect) {
                                                  return 'This module doesn\'t have Super Dev Mode enabled.';
                                                  }
                                                      
                                                  // looks okay
                                                  return null;
                                                  }-*/;

    /**
     * Tells the GWT application to replace itself with a different version that
     * the code server is serving, then reloads the page. (This only works if
     * the GWT application was compiled with the new dev mode hook turned on in
     * the GWT linker.)
     */
    public native void reloadInDevMode() /*-{
                                         var moduleName = this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::moduleName;
                                         var serverUrl = this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::serverUrl;
                                             
                                         var key = '__gwtDevModeHook:' + moduleName;
                                         window.sessionStorage[key] = serverUrl + '/' + moduleName + '/' +
                                         moduleName + '.nocache.js';
                                         $wnd.location.reload();
                                         }-*/;

    /**
     * Turns dev mode off for the given module, then reloads the page.
     * @param moduleName The modules name
     */
    private native void reloadWithoutDevMode(String moduleName) /*-{
                                                                var key = '__gwtDevModeHook:' + this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::moduleName;
                                                                window.sessionStorage.removeItem(key);
                                                                $wnd.location.reload();
                                                                }-*/;

    public boolean isDevModeInjected() {
        return injected || isModuleOnCodeServer();
    }

    public Element getDevModeOnScriptElement() {
        HeadElement head = Document.get().getHead();
        NodeList<Node> childNodes = head.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node childNode = childNodes.getItem(i);
            if (Element.is(childNode)) {
                Element child = childNode.cast();
                if (ScriptElement.is(child)) {
                    ScriptElement scriptElement = ScriptElement.as(child);
                    String scriptSrc = scriptElement.getSrc();
                    if (scriptSrc != null && scriptSrc.contains("dev_mode_on.js")) {
                        return child;
                    }
                }
            }
        }
        return null;
    }

    private native void setCompiling(boolean compiling) /*-{
                                                        $wnd.__gwt_bookmarklet_globals.compiling = compiling;
                                                        }-*/;

    private native void injectDevModeOn() /*-{
                                          $wnd.__gwt_bookmarklet_globals.compiling = true;
                                              
                                          var serverUrl = this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::serverUrl;
                                          var moduleName = this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::moduleName;
                                          $wnd.__gwt_bookmarklet_params = {'server_url': serverUrl + '/', 'module_name':moduleName};
                                          var s = $wnd.document.createElement('script');
                                          s.src = serverUrl + '/dev_mode_on.js';
                                          void($wnd.document.getElementsByTagName('head')[0].appendChild(s));
                                              
                                          this.@nz.co.doltech.gwt.sdm.SuperDevCompiler::onInjected()();
                                          }-*/;

    private void onInjected() {
        Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
            int maxTries = 0;

            @Override
            public boolean execute() {
                Element devModeOn = getDevModeOnScriptElement();
                if (devModeOn != null && isModuleOnCodeServer()) {
                    logger.fine("dev_mode_on.js is now injected! (took " + maxTries + " tries)");
                    devModeOn.removeFromParent();

                    injected = true;
                    executeInjectedCallbacks();

                    setCompiling(false);
                    return false;
                } else {
                    logger.fine("dev_mode_on.js is not injected yet! (" + maxTries + " tries)");
                    boolean maxed = maxTries++ < 20;
                    setCompiling(!maxed);
                    return maxed;
                }
            }
        }, 200);
    }

    private void executeInjectedCallbacks() {
        for (InjectedCallback callback : injectedCallbacks) {
            if (callback != null) {
                callback.onInjected();
            }
        }
    }

    /**
     * Add an injection callback to execute when super dev mode is injected.
     */
    public InjectedCallback addInjectedCallback(InjectedCallback injectedCallback) {
        if (!injectedCallbacks.contains(injectedCallback)) {
            injectedCallbacks.add(injectedCallback);

            // Check if dev mode already injected
            if (isDevModeInjected()) {
                executeInjectedCallbacks();
            }
        }
        return injectedCallback;
    }

    /**
     * Remove an existing injected callback.
     */
    public boolean removeInjectedCallback(InjectedCallback injectedCallback) {
        return injectedCallback != null && injectedCallbacks.remove(injectedCallback);
    }

    /**
     * Add a compile start callback to execute when the compilation starts.
     */
    public StartedCallback addCompileStartCallback(StartedCallback startCallback) {
        if (!startCallbacks.contains(startCallback)) {
            startCallbacks.add(startCallback);
        }
        return startCallback;
    }

    /**
     * Remove an existing started callback.
     */
    public boolean removeCompileStartCallback(StartedCallback startCallback) {
        return startCallback != null && startCallbacks.remove(startCallback);
    }

    /**
     * Add a poll callback each time a poll is sent to the CodeServer.
     */
    public PollCallback addPollCallback(PollCallback pollCallback) {
        if (!pollCallbacks.contains(pollCallback)) {
            pollCallbacks.add(pollCallback);
        }
        return pollCallback;
    }

    /**
     * Remove an existing poll callback.
     */
    public boolean removePollCallback(PollCallback pollCallback) {
        return pollCallback != null && pollCallbacks.remove(pollCallback);
    }

    /**
     * Add a compile complete callback that is called when the compile is completed.
     */
    public CompletedCallback addCompileCompleteCallback(CompletedCallback completeCallback) {
        if (!completeCallbacks.contains(completeCallback)) {
            completeCallbacks.add(completeCallback);
        }
        return completeCallback;
    }

    /**
     * Remove an existing complete callback.
     */
    public boolean removeCompileCompleteCallback(CompletedCallback completeCallback) {
        return completeCallback != null && completeCallbacks.remove(completeCallback);
    }

    /**
     * Add a compile failed callback that is called when a compile fails.
     */
    public FailedCallback addCompileFailedCallback(FailedCallback failedCallback) {
        if (!failedCallbacks.contains(failedCallback)) {
            failedCallbacks.add(failedCallback);
        }
        return failedCallback;
    }

    /**
     * Remove an existing failed callback.
     */
    public boolean removeCompileFailedCallback(FailedCallback failedCallback) {
        return failedCallback != null && failedCallbacks.remove(failedCallback);
    }
}