package com.intellij.lang.javascript.flex.debug; import com.intellij.icons.AllIcons; import com.intellij.javascript.flex.resolve.ActionScriptClassResolver; import com.intellij.lang.javascript.flex.FlexUtils; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.impl.scopes.ModuleWithDependenciesScope; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiCompiledFile; import com.intellij.psi.PsiElement; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.breakpoints.XBreakpointHandler; import com.intellij.xdebugger.breakpoints.XBreakpointProperties; import com.intellij.xdebugger.breakpoints.XBreakpointType; import com.intellij.xdebugger.breakpoints.XLineBreakpoint; import gnu.trove.TIntObjectHashMap; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; /** * @author Maxim.Mossienko * Date: 30.01.2009 * Time: 9:44:23 */ public class FlexBreakpointsHandler { public interface BreakpointTypeProvider { Class<? extends XBreakpointType<XLineBreakpoint<XBreakpointProperties>, ?>> getBreakpointTypeClass(); } private static final ExtensionPointName<BreakpointTypeProvider> BREAKPOINT_TYPE_PROVIDER_EP = ExtensionPointName.create("com.intellij.flex.breakpoint.type.provider"); private final FlexDebugProcess myDebugProcess; private int lastBreakpointId; private final Collection<XBreakpointHandler<?>> myBreakpointHandlers; private final TObjectIntHashMap<XLineBreakpoint<XBreakpointProperties>> myBreakpointToIndexMap = new TObjectIntHashMap<>(); private final TIntObjectHashMap<XLineBreakpoint<XBreakpointProperties>> myIndexToBreakpointMap = new TIntObjectHashMap<>(); FlexBreakpointsHandler(FlexDebugProcess debugProcess) { myDebugProcess = debugProcess; myBreakpointHandlers = new ArrayList<>(); myBreakpointHandlers.add(new MyBreakpointHandler(FlexBreakpointType.class)); for (BreakpointTypeProvider provider : BREAKPOINT_TYPE_PROVIDER_EP.getExtensions()) { myBreakpointHandlers.add(new MyBreakpointHandler(provider.getBreakpointTypeClass())); } } void updateBreakpointStatusToInvalid(XLineBreakpoint<XBreakpointProperties> breakpoint) { if (breakpoint != null) { myDebugProcess.getSession().updateBreakpointPresentation(breakpoint, AllIcons.Debugger.Db_invalid_breakpoint, null); } } void updateBreakpointStatusToVerified(String breakPointNumber) { int spacePos = breakPointNumber.indexOf(' '); if (spacePos != -1) breakPointNumber = breakPointNumber.substring(0, spacePos); // "24 at 0xf3103" final int index = Integer.parseInt(breakPointNumber); final XLineBreakpoint<XBreakpointProperties> breakpoint = myIndexToBreakpointMap.get(index); if (breakpoint != null) { myDebugProcess.getSession().updateBreakpointPresentation(breakpoint, AllIcons.Debugger.Db_verified_breakpoint, null); } else { // run to cursor } } private String buildInsertBreakpointCommandText(XSourcePosition sourcePosition) { String marker = myDebugProcess.resolveFileReference(sourcePosition.getFile()); return "break " + marker + ":" + (sourcePosition.getLine() + 1); } void handleRunToPosition(XSourcePosition position, FlexDebugProcess flexDebugProcess) { flexDebugProcess.sendCommand( new CompositeDebuggerCommand(new InsertBreakpointCommand(position), new FlexDebugProcess.ContinueCommand())); } public XBreakpointHandler<?>[] getBreakpointHandlers() { return myBreakpointHandlers.toArray(new XBreakpointHandler[myBreakpointHandlers.size()]); } XLineBreakpoint<XBreakpointProperties> getBreakpointByIndex(int breakpointId) { return myIndexToBreakpointMap.get(breakpointId); } private static String getRemoveBreakpointCommandText(int breakPointIndex) { return "delete " + breakPointIndex; } private class MyBreakpointHandler extends XBreakpointHandler<XLineBreakpoint<XBreakpointProperties>> { private MyBreakpointHandler(@NotNull Class<? extends XBreakpointType<XLineBreakpoint<XBreakpointProperties>, ?>> breakpointTypeClass) { super(breakpointTypeClass); } public void registerBreakpoint(@NotNull final XLineBreakpoint<XBreakpointProperties> breakpoint) { final XSourcePosition position = breakpoint.getSourcePosition(); if (position != null) { if (isValidSourceBreakpoint(position)) { myDebugProcess.sendCommand(new InsertBreakpointCommand(breakpoint)); } } } private boolean isValidSourceBreakpoint(XSourcePosition position) { final Project project = myDebugProcess.getSession().getProject(); final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); final VirtualFile file = position.getFile(); VirtualFile rootForFile = fileIndex.getSourceRootForFile(file); // project sources, SDK or or SWC library sources if (rootForFile == null) { rootForFile = fileIndex.getClassRootForFile(file); // raw AS library sources } if (rootForFile == null) { return false; } final Module module = myDebugProcess.getModule(); if (module == null) return false; final ModuleWithDependenciesScope scope = FlexUtils.getModuleWithDependenciesAndLibrariesScope(module, myDebugProcess.getBC(), myDebugProcess.isFlexUnit()); if (scope.contains(file) || isInSourcesOfLibraryInScope(fileIndex, file, scope)) { return true; } if (DumbService.getInstance(project).isDumb()) { return false; } final String relPath = VfsUtilCore.getRelativePath(file.getParent(), rootForFile, '.'); final String fqn = StringUtil.getQualifiedName(relPath, file.getNameWithoutExtension()); final PsiElement clazz = ActionScriptClassResolver.findClassByQNameStatic(fqn, scope); // ignore breakpoints in classes that are out of scope of debugged BC if this scope also contains class with the same fqn; // but if resolved class is in decompiled code (library.swf inside *.swc file) then there's a chance that source file for this library // is not configured properly (or it is in the unrelated Haxe module), in this case allow to set breakpoint. return clazz == null || clazz.getContainingFile() instanceof PsiCompiledFile; } private boolean isInSourcesOfLibraryInScope(final ProjectFileIndex fileIndex, final VirtualFile file, final GlobalSearchScope scope) { if (!fileIndex.isInLibrarySource(file)) { return false; } for (OrderEntry entry : fileIndex.getOrderEntriesForFile(file)) { final VirtualFile[] classesRoots = entry.getFiles(OrderRootType.CLASSES); for (VirtualFile root : classesRoots) { if (scope.contains(root)) { return true; } } } return false; } public void unregisterBreakpoint(@NotNull final XLineBreakpoint<XBreakpointProperties> breakpoint, final boolean temporary) { final XSourcePosition position = breakpoint.getSourcePosition(); if (position != null && isValidSourceBreakpoint(position)) { myDebugProcess.sendCommand(new RemoveBreakpointCommand(breakpoint)); } } } private class InsertBreakpointCommand extends DebuggerCommand { private @Nullable XLineBreakpoint<XBreakpointProperties> myBreakpoint; // null if this is breakpoint for 'Run To Cursor' action private @NotNull final XSourcePosition mySourcePosition; InsertBreakpointCommand(@NotNull XSourcePosition sourcePosition) { super(buildInsertBreakpointCommandText(sourcePosition), CommandOutputProcessingType.SPECIAL_PROCESSING); mySourcePosition = sourcePosition; } InsertBreakpointCommand(@NotNull XLineBreakpoint<XBreakpointProperties> _breakpoint) { this(_breakpoint.getSourcePosition()); myBreakpoint = _breakpoint; } @Override CommandOutputProcessingMode onTextAvailable(final String s) { int index; if ((index = s.indexOf(FlexDebugProcess.BREAKPOINT_MARKER)) != -1) { if (s.contains(" created")) { registerBreakPoint(); } else if (s.contains("not set; no executable code")) { updateBreakpointStatusToInvalid(myBreakpoint); } else { // Breakpoint 2: file A.mxml, line 15 final int from = index + FlexDebugProcess.BREAKPOINT_MARKER.length(); if (s.contains("file")) { // Breakpoint 2: file A.mxml, line 15 registerBreakPoint(); updateBreakpointStatusToVerified(s.substring(from, s.indexOf(':', from))); } } } else if (s.contains(FlexDebugProcess.AMBIGUOUS_MATCHING_FILE_NAMES)) { if (myDebugProcess.getFileId(mySourcePosition.getFile().getPath()) != null) { final DebuggerCommand command = myBreakpoint == null ? new InsertBreakpointCommand(mySourcePosition) : new InsertBreakpointCommand(myBreakpoint); myDebugProcess.sendAndProcessOneCommand(command, null); } else { updateBreakpointStatusToInvalid(myBreakpoint); } } return CommandOutputProcessingMode.DONE; } private void registerBreakPoint() { final int breakPointIndex = ++lastBreakpointId; if (myBreakpoint != null) { myBreakpointToIndexMap.put(myBreakpoint, breakPointIndex); myIndexToBreakpointMap.put(breakPointIndex, myBreakpoint); } } } class RemoveBreakpointCommand extends DebuggerCommand { private int myBreakpointIndex; private final XLineBreakpoint<XBreakpointProperties> myBreakpoint; // null if this is breakpoint for 'Run To Cursor' action private static final int UNKNOWN_ID = -1; RemoveBreakpointCommand(int breakPointIndex, @Nullable XLineBreakpoint<XBreakpointProperties> breakpoint) { super(getRemoveBreakpointCommandText(breakPointIndex), CommandOutputProcessingType.SPECIAL_PROCESSING); myBreakpointIndex = breakPointIndex; myBreakpoint = breakpoint; } RemoveBreakpointCommand(@NotNull XLineBreakpoint<XBreakpointProperties> breakpoint) { this(UNKNOWN_ID, breakpoint); // the breakpoint can be not registered yet so we will wait till command posting (getText) } @NotNull @Override String getText() { if (myBreakpointIndex == UNKNOWN_ID) { myBreakpointIndex = myBreakpointToIndexMap.get(myBreakpoint); return getRemoveBreakpointCommandText(myBreakpointIndex); } return super.getText(); } @Override CommandOutputProcessingMode onTextAvailable(@NonNls String s) { myBreakpointToIndexMap.remove(myBreakpoint); myIndexToBreakpointMap.remove(myBreakpointIndex); return super.onTextAvailable(s); } } }