/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.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.dart.tools.debug.core.dartium; import com.google.common.annotations.VisibleForTesting; import com.google.dart.tools.core.DartCore; import com.google.dart.tools.core.DartCoreDebug; import com.google.dart.tools.debug.core.DartDebugCorePlugin; import com.google.dart.tools.debug.core.DartLaunchConfigWrapper; import com.google.dart.tools.debug.core.breakpoints.DartBreakpoint; import com.google.dart.tools.debug.core.util.IResourceResolver; import com.google.dart.tools.debug.core.webkit.WebkitBreakpoint; import com.google.dart.tools.debug.core.webkit.WebkitCallback; import com.google.dart.tools.debug.core.webkit.WebkitLocation; import com.google.dart.tools.debug.core.webkit.WebkitResult; import com.google.dart.tools.debug.core.webkit.WebkitScript; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.runtime.Path; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IBreakpointListener; import org.eclipse.debug.core.model.IBreakpoint; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Handle adding a removing breakpoints to the WebKit connection for the DartiumDebugTarget class. */ public class BreakpointManager implements IBreakpointListener, DartBreakpointManager { public static class NullBreakpointManager implements DartBreakpointManager { public NullBreakpointManager() { } @Override public void connect() throws IOException { } @Override public void dispose(boolean deleteAll) { } @Override public DartBreakpoint getBreakpointFor(WebkitLocation location) { return null; } @Override public void handleBreakpointResolved(WebkitBreakpoint breakpoint) { } @Override public void handleGlobalObjectCleared() { } } private static String PACKAGES_DIRECTORY_PATH = "/packages/"; private static String FILE_SPEC = "file://"; private static String LIB_DIRECTORY_PATH = "/lib/"; private DartiumDebugTarget debugTarget; private Map<IBreakpoint, List<String>> breakpointToIdMap = new HashMap<IBreakpoint, List<String>>(); private Map<String, DartBreakpoint> breakpointsToUpdateMap = new HashMap<String, DartBreakpoint>(); private List<IBreakpoint> ignoredBreakpoints = new ArrayList<IBreakpoint>(); public BreakpointManager(DartiumDebugTarget debugTarget) { this.debugTarget = debugTarget; } @Override public void breakpointAdded(IBreakpoint breakpoint) { if (debugTarget.supportsBreakpoint(breakpoint)) { try { addBreakpoint((DartBreakpoint) breakpoint); } catch (IOException exception) { if (!debugTarget.isTerminated()) { DartDebugCorePlugin.logError(exception); } } } } @Override public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) { // We generate this change event in the handleBreakpointResolved() method - ignore one // instance of the event. if (ignoredBreakpoints.contains(breakpoint)) { ignoredBreakpoints.remove(breakpoint); return; } if (debugTarget.supportsBreakpoint(breakpoint)) { breakpointRemoved(breakpoint, delta); breakpointAdded(breakpoint); } } @Override public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) { if (debugTarget.supportsBreakpoint(breakpoint)) { List<String> breakpointIds = breakpointToIdMap.get(breakpoint); if (breakpointIds != null) { for (String breakpointId : breakpointIds) { breakpointsToUpdateMap.remove(breakpointId); try { debugTarget.getWebkitConnection().getDebugger().removeBreakpoint(breakpointId); } catch (IOException exception) { if (!debugTarget.isTerminated()) { DartDebugCorePlugin.logError(exception); } } } } } } @Override public void connect() throws IOException { IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints( DartDebugCorePlugin.DEBUG_MODEL_ID); for (IBreakpoint breakpoint : breakpoints) { if (debugTarget.supportsBreakpoint(breakpoint)) { addBreakpoint((DartBreakpoint) breakpoint); } } DebugPlugin.getDefault().getBreakpointManager().addBreakpointListener(this); } @Override public void dispose(boolean deleteAll) { // Null check for when the editor is shutting down. if (DebugPlugin.getDefault() != null) { if (deleteAll) { try { for (List<String> ids : breakpointToIdMap.values()) { if (ids != null) { for (String id : ids) { debugTarget.getWebkitConnection().getDebugger().removeBreakpoint(id); } } } } catch (IOException exception) { if (!debugTarget.isTerminated()) { DartDebugCorePlugin.logError(exception); } } } DebugPlugin.getDefault().getBreakpointManager().removeBreakpointListener(this); } } @Override public DartBreakpoint getBreakpointFor(WebkitLocation location) { IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints( DartDebugCorePlugin.DEBUG_MODEL_ID); WebkitScript script = debugTarget.getWebkitConnection().getDebugger().getScript( location.getScriptId()); if (script == null) { return null; } String url = script.getUrl(); int line = WebkitLocation.webkitToElipseLine(location.getLineNumber()); for (IBreakpoint bp : breakpoints) { if (debugTarget.supportsBreakpoint(bp)) { DartBreakpoint breakpoint = (DartBreakpoint) bp; if (breakpoint.getLine() == line) { IFile file = breakpoint.getFile(); String bpUrl = null; if (file != null) { bpUrl = getResourceResolver().getUrlForResource(file); } else { bpUrl = breakpoint.getFilePath(); } if (bpUrl != null && bpUrl.equals(url)) { return breakpoint; } } } } return null; } @VisibleForTesting public String getPackagePath(String regex, IResource resource, String fileLocation) { String filePath = regex; String packagePath = null; if (DartCoreDebug.ENABLE_ANALYSIS_SERVER) { filePath = fileLocation; packagePath = resolvePathToPackage(resource, filePath); if (packagePath != null && packagePath.startsWith(DartCore.PACKAGE_SCHEME_SPEC)) { packagePath = packagePath.substring(DartCore.PACKAGE_SCHEME_SPEC.length()); } if (packagePath != null && packagePath.startsWith(FILE_SPEC)) { packagePath = packagePath.substring(FILE_SPEC.length()); if (DartCore.isWindows() && packagePath.startsWith("/")) { packagePath = packagePath.substring(1); } try { // server returns a url encoded string, so decode before matching packagePath = URLDecoder.decode(packagePath, "UTF-8"); } catch (UnsupportedEncodingException e) { DartCore.logError(e); } if (new Path(packagePath).equals(new Path(fileLocation))) { return null; } } } else { Path path = new Path(regex); int i = 0; if (regex.indexOf(LIB_DIRECTORY_PATH) != -1) { // remove all segments after "lib", they show path in the package while (i < path.segmentCount() && !path.segment(i).equals("lib")) { i++; } } else { i = 1; } if (path.segmentCount() > i + 1) { filePath = new Path(regex).removeLastSegments(path.segmentCount() - (i + 1)).toString(); } packagePath = resolvePathToPackage(resource, filePath); if (packagePath != null) { packagePath += "/" + path.removeFirstSegments(i + 1); } } return packagePath; } @Override public void handleBreakpointResolved(WebkitBreakpoint webkitBreakpoint) { DartBreakpoint breakpoint = breakpointsToUpdateMap.get(webkitBreakpoint.getBreakpointId()); if (breakpoint != null) { int eclipseLine = WebkitLocation.webkitToElipseLine(webkitBreakpoint.getLocation().getLineNumber()); if (breakpoint.getLine() != eclipseLine) { ignoredBreakpoints.add(breakpoint); String message = "[breakpoint in " + breakpoint.getName() + " moved from line " + breakpoint.getLine() + " to " + eclipseLine + "]"; debugTarget.writeToStdout(message); breakpoint.updateLineNumber(eclipseLine); } } } @Override public void handleGlobalObjectCleared() { } @VisibleForTesting protected String resolvePathToPackage(IResource resource, String filePath) { return debugTarget.getUriToFileResolver().getUriForPath(filePath); } void addToBreakpointMap(IBreakpoint breakpoint, String id, boolean trackChanges) { synchronized (breakpointToIdMap) { if (breakpointToIdMap.get(breakpoint) == null) { breakpointToIdMap.put(breakpoint, new ArrayList<String>()); } breakpointToIdMap.get(breakpoint).add(id); if (trackChanges) { breakpointsToUpdateMap.put(id, (DartBreakpoint) breakpoint); } } } private void addBreakpoint(final DartBreakpoint breakpoint) throws IOException { if (breakpoint.isBreakpointEnabled()) { String regex = null; IFile file = breakpoint.getFile(); if (file != null) { regex = getResourceResolver().getUrlRegexForResource(file); } else { regex = getUrlRegexForNonResource(breakpoint); } if (regex == null) { return; } int line = WebkitLocation.eclipseToWebkitLine(breakpoint.getLine()); int packagesIndex = regex.indexOf(PACKAGES_DIRECTORY_PATH); if (packagesIndex != -1) { // convert xxx/packages/foo/foo.dart to *foo/foo.dart regex = regex.substring(packagesIndex + PACKAGES_DIRECTORY_PATH.length()); } else if (file != null && isInSelfLinkedLib(file)) { // Check if source is located in the "lib" directory; if there is a link to it from the // packages directory breakpoint should be /packages/... String packageName = DartCore.getSelfLinkedPackageName(file); if (packageName != null) { // Create a breakpoint for self-links. int libIndex = regex.lastIndexOf(LIB_DIRECTORY_PATH); String packageRegex = packageName + "/" + regex.substring(libIndex + LIB_DIRECTORY_PATH.length()); debugTarget.getWebkitConnection().getDebugger().setBreakpointByUrl( null, packageRegex, line, -1, new WebkitCallback<String>() { @Override public void handleResult(WebkitResult<String> result) { if (!result.isError()) { addToBreakpointMap(breakpoint, result.getResult(), false); } } }); } } // check if it is a file in a package, if so replace regex with package uri path // TODO(keertip): revisit when moved to calling pub for package info IResource resource = null; if (file != null) { resource = file; } else { DartLaunchConfigWrapper wrapper = new DartLaunchConfigWrapper( debugTarget.getLaunch().getLaunchConfiguration()); resource = wrapper.getProject(); } String packagePath = getPackagePath(regex, resource, breakpoint.getActualFilePath()); if (packagePath != null) { regex = packagePath; } debugTarget.getWebkitConnection().getDebugger().setBreakpointByUrl( null, regex, line, -1, new WebkitCallback<String>() { @Override public void handleResult(WebkitResult<String> result) { if (!result.isError()) { addToBreakpointMap(breakpoint, result.getResult(), true); } } }); // Handle source mapped breakpoints - set an additional breakpoint if the file is under // source mapping. SourceMapManager sourceMapManager = debugTarget.getSourceMapManager(); if (file != null) { if (sourceMapManager != null && sourceMapManager.isMapTarget(file)) { List<SourceMapManager.SourceLocation> locations = sourceMapManager.getReverseMappingsFor( file, line); for (SourceMapManager.SourceLocation location : locations) { String mappedRegex = getResourceResolver().getUrlRegexForResource(location.getFile()); if (mappedRegex != null) { if (DartDebugCorePlugin.LOGGING) { System.out.println("breakpoint [" + regex + "," + breakpoint.getLine() + ",-1] ==> mapped to [" + mappedRegex + "," + location.getLine() + "," + location.getColumn() + "]"); } debugTarget.getWebkitConnection().getDebugger().setBreakpointByUrl( null, mappedRegex, location.getLine(), location.getColumn(), new WebkitCallback<String>() { @Override public void handleResult(WebkitResult<String> result) { if (!result.isError()) { addToBreakpointMap(breakpoint, result.getResult(), false); } } }); } } } } } } private IResourceResolver getResourceResolver() { return debugTarget.getResourceResolver(); } private String getUrlRegexForNonResource(DartBreakpoint breakpoint) { String url = breakpoint.getFilePath(); Path path = new Path(url); int i = 0; if (url.indexOf("lib") != -1) { // find lib while (i < path.segmentCount() && !path.segment(i).equals("lib")) { i++; } } // get the library name if (i > 2) { url = path.removeFirstSegments(i - 2).toPortableString(); } return url; } private boolean isInSelfLinkedLib(IFile file) { if (file == null) { return false; } return isPubLib(file.getParent()); } private boolean isPubLib(IContainer container) { if (container.getParent() == null) { return false; } if (container.getName().equals("lib")) { if (DartCore.isApplicationDirectory(container.getParent())) { return true; } } if (container.getParent() instanceof IWorkspaceRoot) { return false; } return isPubLib(container.getParent()); } }