Java tutorial
/* * Copyright 2010 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.speedtracer.client; import com.google.gwt.coreext.client.IterableFastStringMap; import com.google.gwt.coreext.client.JSON; import com.google.gwt.coreext.client.JSON.JSONParseException; import com.google.gwt.xhr.client.XMLHttpRequest; import com.google.speedtracer.client.SourceViewer.SourcePresenter; import com.google.speedtracer.client.SymbolServerManifest.ResourceSymbolInfo; import com.google.speedtracer.client.model.JsSymbol; import com.google.speedtracer.client.model.JsSymbolMap; import com.google.speedtracer.client.util.Url; import com.google.speedtracer.client.util.Xhr; import com.google.speedtracer.client.util.Xhr.XhrCallback; import java.util.ArrayList; import java.util.List; /** * Class resolving the symbol map file and original source files for * re-symbolization. */ public class SymbolServerController { /** * Listener interface for getting notified when a symbols or source are * available. */ public interface Callback { /** * Called if the request for the symbols failed. */ void onSymbolsFetchFailed(int errorReason); /** * Called when the symbols for a resource have been loaded. */ void onSymbolsReady(JsSymbolMap symbols); } /** * Callback interface for renderers that wish to consume a resymbolized * symbol. */ public interface Resymbolizeable { void reSymbolize(String sourceServer, SourceViewerServer sourceViewerServer, JsSymbol sourceSymbol, SourcePresenter sourcePresenter); } /** * A request that has been queued for servicing. */ private class PendingRequest { final Callback callback; final String resourceUrl; PendingRequest(String resourceUrl, Callback callback) { this.resourceUrl = resourceUrl; this.callback = callback; } } public static final int ERROR_MANIFEST_NOT_LOADED = 0; public static final int ERROR_SYMBOL_FETCH_FAIL = 1; private static IterableFastStringMap<JsSymbolMap> resourceSymbols = new IterableFastStringMap<JsSymbolMap>(); /** * Retrieve a Symbol Map for a particular resource URL. * * @param resource The resource we want the symbol mapping for. * @return The symbol map. */ public static JsSymbolMap get(String resource) { return resourceSymbols.get(resource); } /** * Inserts a symbol map into the set of available symbol maps, keyed by * resource URL. * * @param resource The resource we want the symbol mapping for. */ static void put(String resource, JsSymbolMap symbols) { resourceSymbols.put(resource, symbols); } private final Url mainResourceUrl; private boolean manifestLoaded = false; private final List<PendingRequest> pendingRequests; private final IterableFastStringMap<List<XhrCallback>> queuedSymbolMapRequests = new IterableFastStringMap<List<XhrCallback>>(); private final Url symbolManifestUrl; private SymbolServerManifest symbolServerManifest; SymbolServerController(Url mainResourceUrl, Url symbolManifestUrl) { this.mainResourceUrl = mainResourceUrl; this.symbolManifestUrl = symbolManifestUrl; this.pendingRequests = new ArrayList<PendingRequest>(); // Start xhr for fetching our associated symbol manifest. init(); } /** * Attempts to resymbolize a given symbol name within a given resource. * * @param resourceUrl the url of the resource that contains the symbol we want * to resymblize * @param symbolName the symbol we want to resymbolize * @param renderer the recipient of the resymbolization * @param sourcePresenter the entity responsible for displaying the source of * the resymbolized symbol */ public void attemptResymbolization(final String resourceUrl, final String symbolName, final Resymbolizeable renderer, final SourcePresenter sourcePresenter) { requestSymbolsFor(resourceUrl, new Callback() { public void onSymbolsFetchFailed(int errorReason) { // TODO (jaimeyap): Do something here... or not. } public void onSymbolsReady(final JsSymbolMap symbols) { // Extract the source symbol. final JsSymbol sourceSymbol = symbols.lookup(symbolName); if (sourceSymbol == null) { return; } // Enhance the rendered frame with the resymbolization. renderer.reSymbolize(symbols.getSourceServer(), symbols.getSourceViewerServer(), sourceSymbol, sourcePresenter); } }); } /** * Cancels all pending requests. */ public void cancelPendingRequests() { pendingRequests.clear(); } /** * Looks up the {@link JsSymbolMap} associated with a particular resource url * and calls the specified callback when it has been fetched. * * If the symbol manifest is not loaded, this method will queue the request to * be serviced as soon as the manifest loads. * * This call may or may not call back synchronously. Do not bank on * asynchronous behavior. * * @param resourceUrl the resource that we intend to get the symbols for. * @param callback the {@link Callback} that gets invoked when the symbols are * loaded. */ public void requestSymbolsFor(final String resourceUrl, final Callback callback) { PendingRequest request = new PendingRequest(resourceUrl, callback); if (!manifestLoaded) { // Queue a pending request. pendingRequests.add(request); return; } serviceRequest(request); } private void init() { Xhr.get(symbolManifestUrl.getUrl(), new XhrCallback() { public void onFail(XMLHttpRequest xhr) { // Let pending requests know that the manifest failed to load. for (PendingRequest request : pendingRequests) { request.callback.onSymbolsFetchFailed(ERROR_MANIFEST_NOT_LOADED); } cancelPendingRequests(); SymbolServerService.unregisterSymbolServerController(mainResourceUrl); if (ClientConfig.isDebugMode()) { Logging.getLogger().logText("Fetching manifest " + symbolManifestUrl.getUrl() + " failed."); } } public void onSuccess(XMLHttpRequest xhr) { // TODO (jaimeyap): This needs to be validated... and we should handle // any parsing errors as well. try { SymbolServerController.this.symbolServerManifest = JSON.parse(xhr.getResponseText()).cast(); } catch (JSONParseException jpe) { if (ClientConfig.isDebugMode()) { Logging.getLogger().logText("Unable to parse manifest for " + symbolManifestUrl.getUrl()); } onFail(xhr); // Treat an invalid manifest like a network failure. return; } SymbolServerController.this.manifestLoaded = true; // Now service all the pending requests. while (!pendingRequests.isEmpty()) { serviceRequest(pendingRequests.remove(0)); } if (ClientConfig.isDebugMode()) { Logging.getLogger().logText("Manifest " + symbolManifestUrl.getUrl() + " loaded."); } } }); } private ResourceSymbolInfo lookupEntryInManifest(String resourceUrl) { ResourceSymbolInfo resourceSymbolInfo = null; // If the resourceUrl begins with a '/' then we assume it is relative to the // origin. String relativeUrl = ""; if (resourceUrl.charAt(0) == '/') { relativeUrl = Url.convertToRelativeUrl(mainResourceUrl.getOrigin(), resourceUrl); resourceSymbolInfo = symbolServerManifest.getResourceSymbolInfo(relativeUrl); } else { // First try looking for the resource using the full URL. resourceSymbolInfo = symbolServerManifest.getResourceSymbolInfo(resourceUrl); // If the lookup was null, then attempt a relative url lookup. if (resourceSymbolInfo == null) { relativeUrl = Url.convertToRelativeUrl(mainResourceUrl.getResourceBase(), resourceUrl); resourceSymbolInfo = symbolServerManifest.getResourceSymbolInfo(relativeUrl); } } if (ClientConfig.isDebugMode() && resourceSymbolInfo == null) { Logging.getLogger().logText("Failed to find url " + resourceUrl + " relative to " + relativeUrl + " base: " + mainResourceUrl.getResourceBase()); } return resourceSymbolInfo; } private void serviceRequest(PendingRequest request) { assert manifestLoaded : "Manifest should be loaded"; final ResourceSymbolInfo resourceSymbolInfo = lookupEntryInManifest(request.resourceUrl); final Callback callback = request.callback; if (resourceSymbolInfo == null) { if (ClientConfig.isDebugMode()) { Logging.getLogger().logText("Resymbolization failed: No symbol info for " + request.resourceUrl); } callback.onSymbolsFetchFailed(ERROR_SYMBOL_FETCH_FAIL); return; } final String symbolMapUrl = resourceSymbolInfo.getSymbolMapUrl(); JsSymbolMap symbolMap = get(symbolMapUrl); // We only want to request and parse for symbolMaps we havn't already // parsed. if (symbolMap == null) { // Create the XhrCallback to service this request. XhrCallback xhrCallback = new XhrCallback() { public void onFail(XMLHttpRequest xhr) { callback.onSymbolsFetchFailed(ERROR_SYMBOL_FETCH_FAIL); dequeuePendingXhrs(symbolMapUrl, xhr, false); if (ClientConfig.isDebugMode()) { Logging.getLogger().logText("Fetching symbol map: " + symbolMapUrl + " failed."); } } public void onSuccess(XMLHttpRequest xhr) { // Double check that another XHR didnt pull it down and parse it. JsSymbolMap fetchedSymbolMap = get(symbolMapUrl); if (fetchedSymbolMap == null) { fetchedSymbolMap = JsSymbolMap.parse(resourceSymbolInfo.getSourceServer(), resourceSymbolInfo.getSourceViewerServer(), resourceSymbolInfo.getType(), xhr.getResponseText()); put(symbolMapUrl, fetchedSymbolMap); } callback.onSymbolsReady(fetchedSymbolMap); dequeuePendingXhrs(symbolMapUrl, xhr, true); if (ClientConfig.isDebugMode()) { Logging.getLogger().logText("Fetched symbol map: " + symbolMapUrl); } } private void dequeuePendingXhrs(String symbolMapUrl, XMLHttpRequest xhr, boolean success) { List<XhrCallback> callbacks = queuedSymbolMapRequests.get(symbolMapUrl); if (callbacks != null) { while (!callbacks.isEmpty()) { XhrCallback callback = callbacks.remove(0); if (success) { callback.onSuccess(xhr); } else { callback.onFail(xhr); } } } } }; // Check to see if we have a request in flight. List<XhrCallback> requestCallbacks = queuedSymbolMapRequests.get(symbolMapUrl); if (requestCallbacks == null) { // Make an entry indicating a request is in flight. queuedSymbolMapRequests.put(symbolMapUrl, new ArrayList<XhrCallback>()); if (ClientConfig.isDebugMode()) { Logging.getLogger().logText( "Fetching symbol map URL: " + symbolManifestUrl.getResourceBase() + symbolMapUrl); } Xhr.get(symbolManifestUrl.getResourceBase() + symbolMapUrl, xhrCallback); } else { // There are pending XHRs out. Which means that we should just queue // this request. requestCallbacks.add(xhrCallback); } } else { // We have already fetched this and parsed it before. Send it to the // callback. callback.onSymbolsReady(symbolMap); } } }