Package com.google.gwt.core.ext.linker.impl

Source Code of com.google.gwt.core.ext.linker.impl.SelectionScriptLinker

/*******************************************************************************
* Copyright 2011 Google Inc. All Rights Reserved.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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 com.google.gwt.core.ext.linker.impl;

import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.AbstractLinker;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.EmittedArtifact;
import com.google.gwt.core.ext.linker.ScriptReference;
import com.google.gwt.core.ext.linker.SelectionProperty;
import com.google.gwt.core.ext.linker.StylesheetReference;
import com.google.gwt.dev.util.Util;
import com.google.gwt.util.tools.Utility;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedSet;

/**
* A base class for Linkers that use an external script to boostrap the GWT
* module. This implementation injects JavaScript Snippets into a JS program
* defined in an external file.
*/
public abstract class SelectionScriptLinker extends AbstractLinker {
  /**
   * TODO(bobv): Move this class into c.g.g.core.linker when HostedModeLinker
   * goes away?
   */

  /**
   * The extension added to demand-loaded fragment files.
   */
  protected static final String FRAGMENT_EXTENSION = ".cache.js";

  /**
   * A subdirectory to hold all the generated fragments.
   */
  protected static final String FRAGMENT_SUBDIR = "deferredjs";

  /**
   * Determines whether or not the URL is relative.
   *
   * @param src the test url
   * @return <code>true</code> if the URL is relative, <code>false</code> if not
   */
  @SuppressWarnings("unused")
  protected static boolean isRelativeURL(String src) {
    // A straight absolute url for the same domain, server, and protocol.
    if (src.startsWith("/")) {
      return false;
    }

    // If it can be parsed as a URL, then it's probably absolute.
    try {
      // Just check to see if it can be parsed, no need to store the result.
      new URL(src);

      // Let's guess that it is absolute (thus, not relative).
      return false;
    } catch (MalformedURLException e) {
      // Do nothing, since it was a speculative parse.
    }

    // Since none of the above matched, let's guess that it's relative.
    return true;
  }

  protected static void replaceAll(StringBuffer buf, String search,
      String replace) {
    int len = search.length();
    for (int pos = buf.indexOf(search); pos >= 0; pos = buf.indexOf(search,
        pos + 1)) {
      buf.replace(pos, pos + len, replace);
    }
  }

  private final Map<CompilationResult, String> compilationStrongNames = new IdentityHashMap<CompilationResult, String>();

  @Override
  public ArtifactSet link(TreeLogger logger, LinkerContext context,
      ArtifactSet artifacts) throws UnableToCompleteException {
    ArtifactSet toReturn = new ArtifactSet(artifacts);

    for (CompilationResult compilation : toReturn.find(CompilationResult.class)) {
      toReturn.addAll(doEmitCompilation(logger, context, compilation));
    }

    toReturn.add(emitSelectionScript(logger, context, artifacts));
    return toReturn;
  }

  protected Collection<EmittedArtifact> doEmitCompilation(TreeLogger logger,
      LinkerContext context, CompilationResult result)
      throws UnableToCompleteException {
    String[] js = result.getJavaScript();
    byte[][] bytes = new byte[js.length][];
    bytes[0] = generatePrimaryFragment(logger, context, result, js);
    for (int i = 1; i < js.length; i++) {
      bytes[i] = Util.getBytes(js[i]);
    }

    Collection<EmittedArtifact> toReturn = new ArrayList<EmittedArtifact>();
    toReturn.add(emitBytes(logger, bytes[0], result.getStrongName()
        + getCompilationExtension(logger, context)));
    for (int i = 1; i < js.length; i++) {
      toReturn.add(emitBytes(logger, bytes[i], FRAGMENT_SUBDIR + File.separator
          + result.getStrongName() + File.separator + i + FRAGMENT_EXTENSION));
    }

    compilationStrongNames.put(result, result.getStrongName());

    return toReturn;
  }

  protected EmittedArtifact emitSelectionScript(TreeLogger logger,
      LinkerContext context, ArtifactSet artifacts)
      throws UnableToCompleteException {
    String selectionScript = generateSelectionScript(logger, context, artifacts);
    selectionScript = context.optimizeJavaScript(logger, selectionScript);

    /*
     * Last modified is important to keep hosted mode refreses from clobbering
     * web mode compiles. We set the timestamp on the hosted mode selection
     * script to the same mod time as the module (to allow updates). For web
     * mode, we just set it to now.
     */
    long lastModified;
    if (artifacts.find(CompilationResult.class).size() == 0) {
      lastModified = context.getModuleLastModified();
    } else {
      lastModified = System.currentTimeMillis();
    }

    return emitString(logger, selectionScript, context.getModuleName()
        + ".nocache.js", lastModified);
  }

  /**
   * Generate the primary fragment. The default implementation is based on
   * {@link #getModulePrefix(TreeLogger, LinkerContext, String, int)} and
   * {@link #getModuleSuffix(TreeLogger, LinkerContext)}.
   */
  protected byte[] generatePrimaryFragment(TreeLogger logger,
      LinkerContext context, CompilationResult result, String[] js)
      throws UnableToCompleteException {
    StringBuffer b = new StringBuffer();
    b.append(getModulePrefix(logger, context, result.getStrongName(), js.length));
    b.append(js[0]);
    b.append(getModuleSuffix(logger, context));
    return Util.getBytes(b.toString());
  }

  protected String generatePropertyProvider(SelectionProperty prop) {
    StringBuffer toReturn = new StringBuffer();

    if (prop.tryGetValue() == null && !prop.isDerived()) {
      toReturn.append("providers['" + prop.getName() + "'] = function()");
      toReturn.append(prop.getPropertyProvider());
      toReturn.append(";");

      toReturn.append("values['" + prop.getName() + "'] = {");
      boolean needsComma = false;
      int counter = 0;
      for (String value : prop.getPossibleValues()) {
        if (needsComma) {
          toReturn.append(",");
        } else {
          needsComma = true;
        }
        toReturn.append("'" + value + "':");
        toReturn.append(counter++);
      }
      toReturn.append("};");
    }

    return toReturn.toString();
  }

  protected String generateScriptInjector(String scriptUrl) {
    if (isRelativeURL(scriptUrl)) {
      return "  if (!__gwt_scriptsLoaded['"
          + scriptUrl
          + "']) {\n"
          + "    __gwt_scriptsLoaded['"
          + scriptUrl
          + "'] = true;\n"
          + "    document.write('<script language=\\\"javascript\\\" src=\\\"'+base+'"
          + scriptUrl + "\\\"></script>');\n" + "  }\n";
    } else {
      return "  if (!__gwt_scriptsLoaded['" + scriptUrl + "']) {\n"
          + "    __gwt_scriptsLoaded['" + scriptUrl + "'] = true;\n"
          + "    document.write('<script language=\\\"javascript\\\" src=\\\""
          + scriptUrl + "\\\"></script>');\n" + "  }\n";
    }
  }

  protected String generateSelectionScript(TreeLogger logger,
      LinkerContext context, ArtifactSet artifacts)
      throws UnableToCompleteException {

    StringBuffer selectionScript;
    try {
      selectionScript = new StringBuffer(
          Utility.getFileFromClassPath(getSelectionScriptTemplate(logger,
              context)));
    } catch (IOException e) {
      logger.log(TreeLogger.ERROR, "Unable to read selection script template",
          e);
      throw new UnableToCompleteException();
    }

    replaceAll(selectionScript, "__MODULE_FUNC__",
        context.getModuleFunctionName());
    replaceAll(selectionScript, "__MODULE_NAME__", context.getModuleName());

    int startPos;

    // Add external dependencies
    startPos = selectionScript.indexOf("// __MODULE_STYLES_END__");
    if (startPos != -1) {
      for (StylesheetReference resource : artifacts.find(StylesheetReference.class)) {
        String text = generateStylesheetInjector(resource.getSrc());
        selectionScript.insert(startPos, text);
        startPos += text.length();
      }
    }

    startPos = selectionScript.indexOf("// __MODULE_SCRIPTS_END__");
    if (startPos != -1) {
      for (ScriptReference resource : artifacts.find(ScriptReference.class)) {
        String text = generateScriptInjector(resource.getSrc());
        selectionScript.insert(startPos, text);
        startPos += text.length();
      }
    }

    // Add property providers
    startPos = selectionScript.indexOf("// __PROPERTIES_END__");
    if (startPos != -1) {
      for (SelectionProperty p : context.getProperties()) {
        String text = generatePropertyProvider(p);
        selectionScript.insert(startPos, text);
        startPos += text.length();
      }
    }

    // Possibly add permutations
    SortedSet<CompilationResult> compilations = artifacts.find(CompilationResult.class);
    startPos = selectionScript.indexOf("// __PERMUTATIONS_END__");
    if (startPos != -1) {
      StringBuffer text = new StringBuffer();
      if (compilations.size() == 0) {
        // Hosted mode link.
        text.append("alert(\"GWT module '"
            + context.getModuleName()
            + "' may need to be (re)compiled\");");
        text.append("return;");

      } else if (compilations.size() == 1) {
        // Just one distinct compilation; no need to evaluate properties
        Iterator<CompilationResult> iter = compilations.iterator();
        CompilationResult result = iter.next();
        text.append("strongName = '" + compilationStrongNames.get(result)
            + "';");
      } else {
        for (CompilationResult r : compilations) {
          for (Map<SelectionProperty, String> propertyMap : r.getPropertyMap()) {
            // unflatten([v1, v2, v3], 'strongName');
            text.append("unflattenKeylistIntoAnswers([");
            boolean needsComma = false;
            for (SelectionProperty p : context.getProperties()) {
              if (!propertyMap.containsKey(p)) {
                continue;
              }

              if (needsComma) {
                text.append(",");
              } else {
                needsComma = true;
              }
              text.append("'" + propertyMap.get(p) + "'");
            }
            text.append("], '").append(compilationStrongNames.get(r)).append(
                "');\n");
          }
        }

        // strongName = answers[compute('p1')][compute('p2')];
        text.append("strongName = answers[");
        boolean needsIndexMarkers = false;
        for (SelectionProperty p : context.getProperties()) {
          if (p.tryGetValue() != null) {
            continue;
          } else if (p.isDerived()) {
            continue;
          }
          if (needsIndexMarkers) {
            text.append("][");
          } else {
            needsIndexMarkers = true;
          }
          text.append("computePropValue('" + p.getName() + "')");
        }
        text.append("];");
      }
      selectionScript.insert(startPos, text);
    }

    return selectionScript.toString();
  }

  /**
   * Generate a Snippet of JavaScript to inject an external stylesheet.
   *
   * <pre>
   * if (!__gwt_stylesLoaded['URL']) {
   *   var l = $doc.createElement('link');
   *   __gwt_styleLoaded['URL'] = l;
   *   l.setAttribute('rel', 'stylesheet');
   *   l.setAttribute('href', HREF_EXPR);
   *   $doc.getElementsByTagName('head')[0].appendChild(l);
   * }
   * </pre>
   */
  protected String generateStylesheetInjector(String stylesheetUrl) {
    String hrefExpr = "'" + stylesheetUrl + "'";
    if (isRelativeURL(stylesheetUrl)) {
      hrefExpr = "base + " + hrefExpr;
    }

    return "if (!__gwt_stylesLoaded['" + stylesheetUrl + "']) {\n           "
        + "  var l = $doc.createElement('link');\n                          "
        + "  __gwt_stylesLoaded['" + stylesheetUrl + "'] = l;\n             "
        + "  l.setAttribute('rel', 'stylesheet');\n                         "
        + "  l.setAttribute('href', " + hrefExpr + ");\n                    "
        + "  $doc.getElementsByTagName('head')[0].appendChild(l);\n         "
        + "}\n";
  }

  protected abstract String getCompilationExtension(TreeLogger logger,
      LinkerContext context) throws UnableToCompleteException;

  /**
   * Get the partial path on which a CompilationResult has been emitted.
   *
   * @return the partial path, or <code>null</code> if the CompilationResult has
   *         not been emitted.
   */
  protected String getCompilationStrongName(CompilationResult result) {
    return compilationStrongNames.get(result);
  }

  /**
   * Compute the beginning of a JavaScript file that will hold the main module
   * implementation.
   */
  protected abstract String getModulePrefix(TreeLogger logger,
      LinkerContext context, String strongName)
      throws UnableToCompleteException;

  /**
   * Compute the beginning of a JavaScript file that will hold the main module
   * implementation. By default, calls
   * {@link #getModulePrefix(TreeLogger, LinkerContext, String)}.
   *
   * @param strongName strong name of the module being emitted
   * @param numFragments the number of fragments for this module, including the
   *          main fragment (fragment 0)
   */
  protected String getModulePrefix(TreeLogger logger, LinkerContext context,
      String strongName, int numFragments) throws UnableToCompleteException {
    return getModulePrefix(logger, context, strongName);
  }

  protected abstract String getModuleSuffix(TreeLogger logger,
      LinkerContext context) throws UnableToCompleteException;

  protected abstract String getSelectionScriptTemplate(TreeLogger logger,
      LinkerContext context) throws UnableToCompleteException;
}
TOP

Related Classes of com.google.gwt.core.ext.linker.impl.SelectionScriptLinker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.