Package com.google.gwt.dev.codeserver

Source Code of com.google.gwt.dev.codeserver.SourceHandler

/*
* Copyright 2011 Google Inc.
*
* 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 com.google.gwt.dev.codeserver;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.codeserver.Pages.ErrorPage;
import com.google.gwt.dev.json.JsonObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* Serves Java source files so that a browser's debugger can display them.
* (This works with browsers that follow the <a
* href="https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit"
* >Source Map Spec</a>, such as Chrome.)
*
* <p>The debugger will first fetch the source map from
* /sourcemaps/[module name]/[strong name]_sourcemap.json. This file contains the names of Java
* source files to download. Each source file will have a path like
* "/sourcemaps/[module name]/src/[filename]".</p>
*/
class SourceHandler {

  /**
   * The URL prefix for all source maps and Java source code.
   */
  static final String SOURCEMAP_PATH = "/sourcemaps/";

  /**
   * The suffix that Super Dev Mode uses in source map URL's.
   */
  private static final String SOURCEMAP_URL_SUFFIX = "_sourcemap.json";

  /**
   * Matches a valid source map json file request.
   *
   * Used to extract the strong name of the permutation:
   *   StrongName_sourceMap0.json
   */
  private static final Pattern SOURCEMAP_FILENAME_PATTERN = Pattern.compile(
      "^(" + WebServer.STRONG_NAME + ")" + Pattern.quote(SOURCEMAP_URL_SUFFIX) + "$");

  /**
   * Matches a valid source map request.
   *
   * Used to extract the module name:
   *   /sourcemaps/ModuleName/.....
   */
  private static final Pattern SOURCEMAP_MODULE_PATTERN = Pattern.compile(
      "^" + SOURCEMAP_PATH + "([^/]+)/");

  static final String SOURCEROOT_TEMPLATE_VARIABLE = "$sourceroot_goes_here$";

  private final OutboxTable outboxes;
  private final JsonExporter exporter;

  SourceHandler(OutboxTable outboxes, JsonExporter exporter) {
    this.outboxes = outboxes;
    this.exporter = exporter;
  }

  static boolean isSourceMapRequest(String target) {
    return getModuleNameFromRequest(target) != null;
  }

  /**
   * The template for the sourcemap location to give the compiler.
   * It contains one template variable, __HASH__ for the strong name.
   */
  static String sourceMapLocationTemplate(String moduleName) {
    return SOURCEMAP_PATH + moduleName + "/__HASH__" + SOURCEMAP_URL_SUFFIX;
  }

  Response handle(String target, HttpServletRequest request, TreeLogger logger)
      throws IOException {
    String moduleName = getModuleNameFromRequest(target);
    if (moduleName == null) {
      throw new RuntimeException("invalid request (shouldn't happen): " + target);
    }

    Outbox box = outboxes.findByOutputModuleName(moduleName);
    if (box == null) {
      return new ErrorPage("No such module: " + moduleName);
    } else if (box.containsStubCompile()) {
      return new ErrorPage("This module hasn't been compiled yet.");
    }

    String rootDir = SOURCEMAP_PATH + moduleName + "/";
    String rest = target.substring(rootDir.length());

    if (rest.isEmpty()) {
      return makeDirectoryListPage(box);
    } else if (rest.equals("gwtSourceMap.json")) {
      // This URL is no longer used by debuggers (we use the strong name) but is used for testing.
      // It's useful not to need the strong name to download the sourcemap.
      // (But this only works when there is one permutation.)
      return makeSourceMapPage(moduleName, box.findSourceMapForOnePermutation(), request);
    } else if (rest.endsWith("/")) {
      return sendFileListPage(box, rest);
    } else if (rest.endsWith(".java")) {
      return makeSourcePage(box, rest, request.getQueryString(), logger);
    } else {
      String strongName = getStrongNameFromSourcemapFilename(rest);
      if (strongName != null) {
        File sourceMap = box.findSourceMap(strongName).getAbsoluteFile();
        return makeSourceMapPage(moduleName, sourceMap, request);
      } else {
        return new ErrorPage("page not found");
      }
    }
  }

  static String getModuleNameFromRequest(String target) {
    Matcher matcher = SOURCEMAP_MODULE_PATTERN.matcher(target);
    return matcher.find() ? matcher.group(1) : null;
  }

  static String getStrongNameFromSourcemapFilename(String target) {
    Matcher matcher = SOURCEMAP_FILENAME_PATTERN.matcher(target);
    return matcher.matches() ? matcher.group(1) : null;
  }

  private Response makeSourceMapPage(final String moduleName, File sourceMap,
      HttpServletRequest request) {

    // Stream the file, substituting the sourceroot variable with the filename.
    // (This is more efficient than parsing the file as JSON.)

    // We need to do this at runtime because we don't know what the hostname will be
    // until we get a request. (For example, some people run the Code Server behind
    // a reverse proxy to support https.)

    String sourceRoot = String.format("http://%s:%d/sourcemaps/%s/", request.getServerName(),
        request.getServerPort(), moduleName);

    final Response barePage = Responses.newTextTemplateResponse("application/json", sourceMap,
        "\"" + SOURCEROOT_TEMPLATE_VARIABLE + "\"",
        "\"" + sourceRoot + "\"");

    // Wrap it in another response to time how long it takes.
    return Responses.newTimedResponse(barePage, "sent source map for module '" + moduleName + "'");
  }

  private Response makeDirectoryListPage(Outbox box) throws IOException {
    SourceMap map = SourceMap.load(box.findSourceMapForOnePermutation());
    JsonObject json = exporter.exportSourceMapDirectoryListVars(box, map);
    return Pages.newHtmlPage("config", json, "directorylist.html");
  }

  private Response sendFileListPage(Outbox box, String rest) throws IOException {

    SourceMap map = SourceMap.load(box.findSourceMapForOnePermutation());
    JsonObject json = exporter.exportSourceMapFileListVars(box, map, rest);
    return Pages.newHtmlPage("config", json, "filelist.html");
  }

  /**
   * Returns a page displaying a Java source file. It will be sent as plain text by default,
   * or as HTML if the query string is equal to "html".
   */
  private Response makeSourcePage(Outbox box, String sourcePath, String query, TreeLogger logger)
      throws IOException {

    InputStream pageBytes = box.openSourceFile(sourcePath);
    if (pageBytes == null) {
      return new ErrorPage("unknown source file: " + sourcePath);
    }

    if (query != null && query.equals("html")) {
      return makeHtmlSourcePage(box, sourcePath, pageBytes, logger);
    } else {
      return Responses.newBinaryStreamResponse("text/plain", pageBytes);
    }
  }

  /**
   * Returns a page that will send a Java source file as HTML. The lines of source
   * that have corresponding JavaScript will be highlighted (as determined by reading the
   * source map).
   */
  private Response makeHtmlSourcePage(Outbox box, final String sourcePath,
      final InputStream pageBytes, TreeLogger logger) throws IOException {

    final ReverseSourceMap sourceMap = ReverseSourceMap.load(logger,
        box.findSourceMapForOnePermutation());

    final File sourceFile = new File(sourcePath);

    return new Response() {
      @Override
      public void send(HttpServletRequest request, HttpServletResponse response, TreeLogger logger)
          throws IOException {
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType("text/html");

        HtmlWriter out = new HtmlWriter(response.getWriter());
        out.startTag("html").nl();
        out.startTag("head").nl();
        out.startTag("title").text(sourceFile.getName() + " (GWT Code Server)").endTag("title").nl();
        out.startTag("style").nl();
        out.text(".unused { color: grey; }").nl();
        out.text(".used { color: black; }").nl();
        out.text(".title { margin-top: 0; }").nl();
        out.endTag("style").nl();
        out.endTag("head").nl();
        out.startTag("body").nl();

        out.startTag("a", "href=", ".").text(sourceFile.getParent()).endTag("a").nl();
        out.startTag("h1", "class=", "title").text(sourceFile.getName()).endTag("h1").nl();

        out.startTag("pre", "class=", "unused").nl();

        BufferedReader lines = new BufferedReader(new InputStreamReader(pageBytes));
        try {
          int lineNumber = 1;
          for (String line = lines.readLine(); line != null; line = lines.readLine()) {
            if (sourceMap.appearsInJavaScript(sourcePath, lineNumber)) {
              out.startTag("span", "class=", "used").text(line).endTag("span").nl();
            } else {
              out.text(line).nl();
            }
            lineNumber++;
          }

        } finally {
          lines.close();
        }
        out.endTag("pre").nl();

        out.endTag("body").nl();
        out.endTag("html").nl();
      }
    };
  }
}
TOP

Related Classes of com.google.gwt.dev.codeserver.SourceHandler

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.