public class LineBreakpoint<P extends JavaBreakpointProperties>
    extends BreakpointWithHighlighter<P> {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.debugger.ui.breakpoints.LineBreakpoint");

  public static final @NonNls Key<LineBreakpoint> CATEGORY =
      BreakpointCategory.lookup("line_breakpoints");

  protected LineBreakpoint(Project project, XBreakpoint xBreakpoint) {
    super(project, xBreakpoint);
  }

  @Override
  protected Icon getDisabledIcon(boolean isMuted) {
    final Breakpoint master =
        DebuggerManagerEx.getInstanceEx(myProject)
            .getBreakpointManager()
            .findMasterBreakpoint(this);
    if (isMuted) {
      return master == null
          ? AllIcons.Debugger.Db_muted_disabled_breakpoint
          : AllIcons.Debugger.Db_muted_dep_line_breakpoint;
    } else {
      return master == null
          ? AllIcons.Debugger.Db_disabled_breakpoint
          : AllIcons.Debugger.Db_dep_line_breakpoint;
    }
  }

  @Override
  protected Icon getSetIcon(boolean isMuted) {
    if (isRemoveAfterHit()) {
      return isMuted
          ? AllIcons.Debugger.Db_muted_temporary_breakpoint
          : AllIcons.Debugger.Db_temporary_breakpoint;
    }
    return isMuted ? AllIcons.Debugger.Db_muted_breakpoint : AllIcons.Debugger.Db_set_breakpoint;
  }

  @Override
  protected Icon getInvalidIcon(boolean isMuted) {
    return isMuted
        ? AllIcons.Debugger.Db_muted_invalid_breakpoint
        : AllIcons.Debugger.Db_invalid_breakpoint;
  }

  @Override
  protected Icon getVerifiedIcon(boolean isMuted) {
    if (isRemoveAfterHit()) {
      return isMuted
          ? AllIcons.Debugger.Db_muted_temporary_breakpoint
          : AllIcons.Debugger.Db_temporary_breakpoint;
    }
    return isMuted
        ? AllIcons.Debugger.Db_muted_verified_breakpoint
        : AllIcons.Debugger.Db_verified_breakpoint;
  }

  @Override
  protected Icon getVerifiedWarningsIcon(boolean isMuted) {
    return isMuted
        ? AllIcons.Debugger.Db_muted_verified_warning_breakpoint
        : AllIcons.Debugger.Db_verified_warning_breakpoint;
  }

  @Override
  public Key<LineBreakpoint> getCategory() {
    return CATEGORY;
  }

  @Override
  protected void createOrWaitPrepare(DebugProcessImpl debugProcess, String classToBeLoaded) {
    if (isInScopeOf(debugProcess, classToBeLoaded)) {
      super.createOrWaitPrepare(debugProcess, classToBeLoaded);
    }
  }

  @Override
  protected void createRequestForPreparedClass(
      final DebugProcessImpl debugProcess, final ReferenceType classType) {
    if (!isInScopeOf(debugProcess, classType.name())) {
      if (LOG.isDebugEnabled()) {
        LOG.debug(
            classType.name()
                + " is out of debug-process scope, breakpoint request won't be created for line "
                + getLineIndex());
      }
      return;
    }
    try {
      List<Location> locations =
          debugProcess.getPositionManager().locationsOfLine(classType, getSourcePosition());
      if (!locations.isEmpty()) {
        for (Location loc : locations) {
          if (LOG.isDebugEnabled()) {
            LOG.debug(
                "Found location [codeIndex="
                    + loc.codeIndex()
                    + "] for reference type "
                    + classType.name()
                    + " at line "
                    + getLineIndex()
                    + "; isObsolete: "
                    + (debugProcess.getVirtualMachineProxy().versionHigher("1.4")
                        && loc.method().isObsolete()));
          }
          if (!acceptLocation(debugProcess, classType, loc)) {
            continue;
          }
          final BreakpointRequest request =
              debugProcess.getRequestsManager().createBreakpointRequest(this, loc);
          debugProcess.getRequestsManager().enableRequest(request);
          if (LOG.isDebugEnabled()) {
            LOG.debug(
                "Created breakpoint request for reference type "
                    + classType.name()
                    + " at line "
                    + getLineIndex()
                    + "; codeIndex="
                    + loc.codeIndex());
          }
        }
      } else {
        // there's no executable code in this class
        debugProcess
            .getRequestsManager()
            .setInvalid(
                this,
                DebuggerBundle.message(
                    "error.invalid.breakpoint.no.executable.code",
                    (getLineIndex() + 1),
                    classType.name()));
        if (LOG.isDebugEnabled()) {
          LOG.debug(
              "No locations of type " + classType.name() + " found at line " + getLineIndex());
        }
      }
    } catch (ClassNotPreparedException ex) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("ClassNotPreparedException: " + ex.getMessage());
      }
      // there's a chance to add a breakpoint when the class is prepared
    } catch (ObjectCollectedException ex) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("ObjectCollectedException: " + ex.getMessage());
      }
      // there's a chance to add a breakpoint when the class is prepared
    } catch (InvalidLineNumberException ex) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("InvalidLineNumberException: " + ex.getMessage());
      }
      debugProcess
          .getRequestsManager()
          .setInvalid(this, DebuggerBundle.message("error.invalid.breakpoint.bad.line.number"));
    } catch (InternalException ex) {
      LOG.info(ex);
    } catch (Exception ex) {
      LOG.info(ex);
    }
    updateUI();
  }

  private static final Pattern ourAnonymousPattern = Pattern.compile(".*\\$\\d*$");

  private static boolean isAnonymousClass(ReferenceType classType) {
    if (classType instanceof ClassType) {
      return ourAnonymousPattern.matcher(classType.name()).matches();
    }
    return false;
  }

  protected boolean acceptLocation(
      final DebugProcessImpl debugProcess, ReferenceType classType, final Location loc) {
    Method method = loc.method();
    // Some frameworks may create synthetic methods with lines mapped to user code, see IDEA-143852
    // if (DebuggerUtils.isSynthetic(method)) { return false; }
    if (isAnonymousClass(classType)) {
      if ((method.isConstructor() && loc.codeIndex() == 0) || method.isBridge()) return false;
    }
    return ApplicationManager.getApplication()
        .runReadAction(
            new Computable<Boolean>() {
              @Override
              public Boolean compute() {
                SourcePosition position = debugProcess.getPositionManager().getSourcePosition(loc);
                if (position == null) return false;
                JavaLineBreakpointType type = getXBreakpointType();
                if (type == null) return true;
                return type.matchesPosition(LineBreakpoint.this, position);
              }
            });
  }

  @Nullable
  protected JavaLineBreakpointType getXBreakpointType() {
    XBreakpointType<?, P> type = myXBreakpoint.getType();
    // Nashorn breakpoints do not contain JavaLineBreakpointType
    if (type instanceof JavaLineBreakpointType) {
      return (JavaLineBreakpointType) type;
    }
    return null;
  }

  private boolean isInScopeOf(DebugProcessImpl debugProcess, String className) {
    final SourcePosition position = getSourcePosition();
    if (position != null) {
      final VirtualFile breakpointFile = position.getFile().getVirtualFile();
      final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
      if (breakpointFile != null
          && fileIndex.isUnderSourceRootOfType(breakpointFile, JavaModuleSourceRootTypes.SOURCES)) {
        if (debugProcess.getSearchScope().contains(breakpointFile)) {
          return true;
        }
        // apply filtering to breakpoints from content sources only, not for sources attached to
        // libraries
        final Collection<VirtualFile> candidates =
            findClassCandidatesInSourceContent(className, debugProcess.getSearchScope(), fileIndex);
        if (LOG.isDebugEnabled()) {
          LOG.debug(
              "Found "
                  + (candidates == null ? "null" : candidates.size())
                  + " candidate containing files for class "
                  + className);
        }
        if (candidates == null) {
          // If no candidates are found in scope then assume that class is loaded dynamically and
          // allow breakpoint
          return true;
        }

        // breakpointFile is not in scope here and there are some candidates in scope
        // for (VirtualFile classFile : candidates) {
        //  if (LOG.isDebugEnabled()) {
        //    LOG.debug("Breakpoint file: " + breakpointFile.getPath()+ "; candidate file: " +
        // classFile.getPath());
        //  }
        //  if (breakpointFile.equals(classFile)) {
        //    return true;
        //  }
        // }
        if (LOG.isDebugEnabled()) {
          final GlobalSearchScope scope = debugProcess.getSearchScope();
          final boolean contains = scope.contains(breakpointFile);
          final Project project = getProject();
          final List<VirtualFile> files =
              ContainerUtil.map(
                  JavaFullClassNameIndex.getInstance().get(className.hashCode(), project, scope),
                  new Function<PsiClass, VirtualFile>() {
                    @Override
                    public VirtualFile fun(PsiClass aClass) {
                      return aClass.getContainingFile().getVirtualFile();
                    }
                  });
          final List<VirtualFile> allFiles =
              ContainerUtil.map(
                  JavaFullClassNameIndex.getInstance()
                      .get(className.hashCode(), project, new EverythingGlobalScope(project)),
                  new Function<PsiClass, VirtualFile>() {
                    @Override
                    public VirtualFile fun(PsiClass aClass) {
                      return aClass.getContainingFile().getVirtualFile();
                    }
                  });
          final VirtualFile contentRoot = fileIndex.getContentRootForFile(breakpointFile);
          final Module module = fileIndex.getModuleForFile(breakpointFile);

          LOG.debug(
              "Did not find '"
                  + className
                  + "' in "
                  + scope
                  + "; contains="
                  + contains
                  + "; contentRoot="
                  + contentRoot
                  + "; module = "
                  + module
                  + "; all files in index are: "
                  + files
                  + "; all possible files are: "
                  + allFiles);
        }

        return false;
      }
    }
    return true;
  }

  @Nullable
  private Collection<VirtualFile> findClassCandidatesInSourceContent(
      final String className, final GlobalSearchScope scope, final ProjectFileIndex fileIndex) {
    final int dollarIndex = className.indexOf("$");
    final String topLevelClassName =
        dollarIndex >= 0 ? className.substring(0, dollarIndex) : className;
    return ApplicationManager.getApplication()
        .runReadAction(
            new Computable<Collection<VirtualFile>>() {
              @Override
              @Nullable
              public Collection<VirtualFile> compute() {
                final PsiClass[] classes =
                    JavaPsiFacade.getInstance(myProject).findClasses(topLevelClassName, scope);
                if (LOG.isDebugEnabled()) {
                  LOG.debug(
                      "Found "
                          + classes.length
                          + " classes "
                          + topLevelClassName
                          + " in scope "
                          + scope);
                }
                if (classes.length == 0) {
                  return null;
                }
                final List<VirtualFile> list = new ArrayList<>(classes.length);
                for (PsiClass aClass : classes) {
                  final PsiFile psiFile = aClass.getContainingFile();

                  if (LOG.isDebugEnabled()) {
                    final StringBuilder msg = new StringBuilder();
                    msg.append("Checking class ").append(aClass.getQualifiedName());
                    msg.append("\n\t").append("PsiFile=").append(psiFile);
                    if (psiFile != null) {
                      final VirtualFile vFile = psiFile.getVirtualFile();
                      msg.append("\n\t").append("VirtualFile=").append(vFile);
                      if (vFile != null) {
                        msg.append("\n\t")
                            .append("isInSourceContent=")
                            .append(
                                fileIndex.isUnderSourceRootOfType(
                                    vFile, JavaModuleSourceRootTypes.SOURCES));
                      }
                    }
                    LOG.debug(msg.toString());
                  }

                  if (psiFile == null) {
                    return null;
                  }
                  final VirtualFile vFile = psiFile.getVirtualFile();
                  if (vFile == null
                      || !fileIndex.isUnderSourceRootOfType(
                          vFile, JavaModuleSourceRootTypes.SOURCES)) {
                    return null; // this will switch off the check if at least one class is from
                                 // libraries
                  }
                  list.add(vFile);
                }
                return list;
              }
            });
  }

  @Override
  protected String calculateEventClass(EvaluationContextImpl context, LocatableEvent event)
      throws EvaluateException {
    String className = null;
    final ObjectReference thisObject = (ObjectReference) context.getThisObject();
    if (thisObject != null) {
      className = thisObject.referenceType().name();
    } else {
      final StackFrameProxyImpl frame = context.getFrameProxy();
      if (frame != null) {
        className = frame.location().declaringType().name();
      }
    }
    return className;
  }

  @Override
  public String getShortName() {
    return getDisplayInfoInternal(false, 30);
  }

  @Override
  public String getDisplayName() {
    return getDisplayInfoInternal(true, -1);
  }

  private String getDisplayInfoInternal(boolean showPackageInfo, int totalTextLength) {
    if (isValid()) {
      final int lineNumber = myXBreakpoint.getSourcePosition().getLine() + 1;
      String className = getClassName();
      final boolean hasClassInfo = className != null && className.length() > 0;
      final String methodName = getMethodName();
      final String displayName = methodName != null ? methodName + "()" : null;
      final boolean hasMethodInfo = displayName != null && displayName.length() > 0;
      if (hasClassInfo || hasMethodInfo) {
        final StringBuilder info = StringBuilderSpinAllocator.alloc();
        try {
          boolean isFile = myXBreakpoint.getSourcePosition().getFile().getName().equals(className);
          String packageName = null;
          if (hasClassInfo) {
            final int dotIndex = className.lastIndexOf(".");
            if (dotIndex >= 0 && !isFile) {
              packageName = className.substring(0, dotIndex);
              className = className.substring(dotIndex + 1);
            }

            if (totalTextLength != -1) {
              if (className.length() + (hasMethodInfo ? displayName.length() : 0)
                  > totalTextLength + 3) {
                int offset = totalTextLength - (hasMethodInfo ? displayName.length() : 0);
                if (offset > 0 && offset < className.length()) {
                  className = className.substring(className.length() - offset);
                  info.append("...");
                }
              }
            }

            info.append(className);
          }
          if (hasMethodInfo) {
            if (isFile) {
              info.append(":");
            } else if (hasClassInfo) {
              info.append(".");
            }
            info.append(displayName);
          }
          if (showPackageInfo && packageName != null) {
            info.append(" (").append(packageName).append(")");
          }
          return DebuggerBundle.message(
              "line.breakpoint.display.name.with.class.or.method", lineNumber, info.toString());
        } finally {
          StringBuilderSpinAllocator.dispose(info);
        }
      }
      return DebuggerBundle.message("line.breakpoint.display.name", lineNumber);
    }
    return DebuggerBundle.message("status.breakpoint.invalid");
  }

  @Nullable
  private static String findOwnerMethod(final PsiFile file, final int offset) {
    if (offset < 0 || file instanceof JspFile) {
      return null;
    }
    if (file instanceof PsiClassOwner) {
      return ApplicationManager.getApplication()
          .runReadAction(
              new Computable<String>() {
                @Override
                public String compute() {
                  final PsiMethod method = DebuggerUtilsEx.findPsiMethod(file, offset);
                  return method != null ? method.getName() : null;
                }
              });
    }
    return null;
  }

  @Override
  public String getEventMessage(LocatableEvent event) {
    final Location location = event.location();
    String sourceName;
    try {
      sourceName = location.sourceName();
    } catch (AbsentInformationException e) {
      sourceName = getFileName();
    }

    final boolean printFullTrace = Registry.is("debugger.breakpoint.message.full.trace");

    StringBuilder builder = new StringBuilder();
    if (printFullTrace) {
      builder.append(
          DebuggerBundle.message(
              "status.line.breakpoint.reached.full.trace",
              DebuggerUtilsEx.getLocationMethodQName(location)));
      try {
        final List<StackFrame> frames = event.thread().frames();
        renderTrace(frames, builder);
      } catch (IncompatibleThreadStateException e) {
        builder.append("Stacktrace not available: ").append(e.getMessage());
      }
    } else {
      builder.append(
          DebuggerBundle.message(
              "status.line.breakpoint.reached",
              DebuggerUtilsEx.getLocationMethodQName(location),
              sourceName,
              getLineIndex() + 1));
    }
    return builder.toString();
  }

  private static void renderTrace(List<StackFrame> frames, StringBuilder buffer) {
    for (final StackFrame stackFrame : frames) {
      final Location location = stackFrame.location();
      buffer.append("\n\t  ").append(ThreadDumpAction.renderLocation(location));
    }
  }

  @Override
  public PsiElement getEvaluationElement() {
    return ContextUtil.getContextElement(getSourcePosition());
  }

  public static LineBreakpoint create(@NotNull Project project, XBreakpoint xBreakpoint) {
    LineBreakpoint breakpoint = new LineBreakpoint(project, xBreakpoint);
    return (LineBreakpoint) breakpoint.init();
  }

  // @Override
  // public boolean canMoveTo(SourcePosition position) {
  //  if (!super.canMoveTo(position)) {
  //    return false;
  //  }
  //  final Document document =
  // PsiDocumentManager.getInstance(getProject()).getDocument(position.getFile());
  //  return canAddLineBreakpoint(myProject, document, position.getLine());
  // }

  public static boolean canAddLineBreakpoint(
      Project project, final Document document, final int lineIndex) {
    if (lineIndex < 0 || lineIndex >= document.getLineCount()) {
      return false;
    }
    final BreakpointManager breakpointManager =
        DebuggerManagerEx.getInstanceEx(project).getBreakpointManager();
    final LineBreakpoint breakpointAtLine =
        breakpointManager.findBreakpoint(
            document, document.getLineStartOffset(lineIndex), CATEGORY);
    if (breakpointAtLine != null) {
      // there already exists a line breakpoint at this line
      return false;
    }
    PsiDocumentManager.getInstance(project).commitDocument(document);

    final boolean[] canAdd = new boolean[] {false};
    XDebuggerUtil.getInstance()
        .iterateLine(
            project,
            document,
            lineIndex,
            new Processor<PsiElement>() {
              @Override
              public boolean process(PsiElement element) {
                if ((element instanceof PsiWhiteSpace)
                    || (PsiTreeUtil.getParentOfType(element, PsiComment.class, false) != null)) {
                  return true;
                }
                PsiElement child = element;
                while (element != null) {

                  final int offset = element.getTextOffset();
                  if (offset >= 0) {
                    if (document.getLineNumber(offset) != lineIndex) {
                      break;
                    }
                  }
                  child = element;
                  element = element.getParent();
                }

                if (child instanceof PsiMethod
                    && child.getTextRange().getEndOffset()
                        >= document.getLineEndOffset(lineIndex)) {
                  PsiCodeBlock body = ((PsiMethod) child).getBody();
                  if (body == null) {
                    canAdd[0] = false;
                  } else {
                    PsiStatement[] statements = body.getStatements();
                    canAdd[0] =
                        statements.length > 0
                            && document.getLineNumber(statements[0].getTextOffset()) == lineIndex;
                  }
                } else {
                  canAdd[0] = true;
                }
                return false;
              }
            });

    return canAdd[0];
  }

  @Nullable
  public String getMethodName() {
    XSourcePosition position = myXBreakpoint.getSourcePosition();
    if (position != null) {
      int offset = position.getOffset();
      return findOwnerMethod(getPsiFile(), offset);
    }
    return null;
  }
}
public class LineBreakpoint extends BreakpointWithHighlighter {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.debugger.ui.breakpoints.LineBreakpoint");

  private String myMethodName;
  public static final @NonNls Key<LineBreakpoint> CATEGORY =
      BreakpointCategory.lookup("line_breakpoints");

  protected LineBreakpoint(Project project) {
    super(project);
  }

  protected LineBreakpoint(Project project, RangeHighlighter highlighter) {
    super(project, highlighter);
  }

  protected Icon getDisabledIcon(boolean isMuted) {
    final Breakpoint master =
        DebuggerManagerEx.getInstanceEx(myProject)
            .getBreakpointManager()
            .findMasterBreakpoint(this);
    if (isMuted) {
      return master == null
          ? AllIcons.Debugger.Db_muted_disabled_breakpoint
          : AllIcons.Debugger.Db_muted_dep_line_breakpoint;
    } else {
      return master == null
          ? AllIcons.Debugger.Db_disabled_breakpoint
          : AllIcons.Debugger.Db_dep_line_breakpoint;
    }
  }

  protected Icon getSetIcon(boolean isMuted) {
    return isMuted ? AllIcons.Debugger.Db_muted_breakpoint : AllIcons.Debugger.Db_set_breakpoint;
  }

  protected Icon getInvalidIcon(boolean isMuted) {
    return isMuted
        ? AllIcons.Debugger.Db_muted_invalid_breakpoint
        : AllIcons.Debugger.Db_invalid_breakpoint;
  }

  protected Icon getVerifiedIcon(boolean isMuted) {
    return isMuted
        ? AllIcons.Debugger.Db_muted_verified_breakpoint
        : AllIcons.Debugger.Db_verified_breakpoint;
  }

  protected Icon getVerifiedWarningsIcon(boolean isMuted) {
    return isMuted
        ? AllIcons.Debugger.Db_muted_verified_warning_breakpoint
        : AllIcons.Debugger.Db_verified_warning_breakpoint;
  }

  public Key<LineBreakpoint> getCategory() {
    return CATEGORY;
  }

  protected void reload(PsiFile file) {
    super.reload(file);
    myMethodName = findMethodName(file, getHighlighter().getStartOffset());
  }

  protected void createOrWaitPrepare(DebugProcessImpl debugProcess, String classToBeLoaded) {
    if (isInScopeOf(debugProcess, classToBeLoaded)) {
      super.createOrWaitPrepare(debugProcess, classToBeLoaded);
    }
  }

  protected void createRequestForPreparedClass(
      final DebugProcessImpl debugProcess, final ReferenceType classType) {
    if (!isInScopeOf(debugProcess, classType.name())) {
      return;
    }
    try {
      List<Location> locs =
          debugProcess.getPositionManager().locationsOfLine(classType, getSourcePosition());
      if (!locs.isEmpty()) {
        for (Location loc : locs) {
          if (LOG.isDebugEnabled()) {
            LOG.debug(
                "Found location [codeIndex="
                    + loc.codeIndex()
                    + "] for reference type "
                    + classType.name()
                    + " at line "
                    + getLineIndex()
                    + "; isObsolete: "
                    + (debugProcess.getVirtualMachineProxy().versionHigher("1.4")
                        && loc.method().isObsolete()));
          }
          BreakpointRequest request =
              debugProcess.getRequestsManager().createBreakpointRequest(LineBreakpoint.this, loc);
          debugProcess.getRequestsManager().enableRequest(request);
          if (LOG.isDebugEnabled()) {
            LOG.debug(
                "Created breakpoint request for reference type "
                    + classType.name()
                    + " at line "
                    + getLineIndex()
                    + "; codeIndex="
                    + loc.codeIndex());
          }
        }
      } else {
        // there's no executable code in this class
        debugProcess
            .getRequestsManager()
            .setInvalid(
                LineBreakpoint.this,
                DebuggerBundle.message(
                    "error.invalid.breakpoint.no.executable.code",
                    (getLineIndex() + 1),
                    classType.name()));
        if (LOG.isDebugEnabled()) {
          LOG.debug(
              "No locations of type " + classType.name() + " found at line " + getLineIndex());
        }
      }
    } catch (ClassNotPreparedException ex) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("ClassNotPreparedException: " + ex.getMessage());
      }
      // there's a chance to add a breakpoint when the class is prepared
    } catch (ObjectCollectedException ex) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("ObjectCollectedException: " + ex.getMessage());
      }
      // there's a chance to add a breakpoint when the class is prepared
    } catch (InvalidLineNumberException ex) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("InvalidLineNumberException: " + ex.getMessage());
      }
      debugProcess
          .getRequestsManager()
          .setInvalid(
              LineBreakpoint.this,
              DebuggerBundle.message("error.invalid.breakpoint.bad.line.number"));
    } catch (InternalException ex) {
      LOG.info(ex);
    } catch (Exception ex) {
      LOG.info(ex);
    }
    updateUI();
  }

  private boolean isInScopeOf(DebugProcessImpl debugProcess, String className) {
    final SourcePosition position = getSourcePosition();
    if (position != null) {
      final VirtualFile breakpointFile = position.getFile().getVirtualFile();
      final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
      if (breakpointFile != null && fileIndex.isInSourceContent(breakpointFile)) {
        // apply filtering to breakpoints from content sources only, not for sources attached to
        // libraries
        final Collection<VirtualFile> candidates =
            findClassCandidatesInSourceContent(className, debugProcess.getSearchScope(), fileIndex);
        if (candidates == null) {
          return true;
        }
        for (VirtualFile classFile : candidates) {
          if (breakpointFile.equals(classFile)) {
            return true;
          }
        }
        return false;
      }
    }
    return true;
  }

  @Nullable
  private Collection<VirtualFile> findClassCandidatesInSourceContent(
      final String className, final GlobalSearchScope scope, final ProjectFileIndex fileIndex) {
    final int dollarIndex = className.indexOf("$");
    final String topLevelClassName =
        dollarIndex >= 0 ? className.substring(0, dollarIndex) : className;
    return ApplicationManager.getApplication()
        .runReadAction(
            new Computable<Collection<VirtualFile>>() {
              @Nullable
              public Collection<VirtualFile> compute() {
                final PsiClass[] classes =
                    JavaPsiFacade.getInstance(myProject).findClasses(topLevelClassName, scope);
                if (classes.length == 0) {
                  return null;
                }
                final List<VirtualFile> list = new ArrayList<VirtualFile>(classes.length);
                for (PsiClass aClass : classes) {
                  final PsiFile psiFile = aClass.getContainingFile();
                  if (psiFile != null) {
                    final VirtualFile vFile = psiFile.getVirtualFile();
                    if (vFile != null && fileIndex.isInSourceContent(vFile)) {
                      list.add(vFile);
                    }
                  }
                }
                return list;
              }
            });
  }

  public boolean evaluateCondition(EvaluationContextImpl context, LocatableEvent event)
      throws EvaluateException {
    if (CLASS_FILTERS_ENABLED) {
      String className = null;
      final ObjectReference thisObject = (ObjectReference) context.getThisObject();
      if (thisObject != null) {
        className = thisObject.referenceType().name();
      } else {
        final StackFrameProxyImpl frame = context.getFrameProxy();
        if (frame != null) {
          className = frame.location().declaringType().name();
        }
      }
      if (className != null) {
        boolean matches = false;
        for (ClassFilter classFilter : getClassFilters()) {
          if (classFilter.isEnabled() && classFilter.matches(className)) {
            matches = true;
            break;
          }
        }
        if (!matches) {
          return false;
        }
        for (ClassFilter classFilter : getClassExclusionFilters()) {
          if (classFilter.isEnabled() && classFilter.matches(className)) {
            return false;
          }
        }
      }
    }
    return super.evaluateCondition(context, event);
  }

  public String toString() {
    return getDescription();
  }

  @Override
  public String getShortName() {
    return getDisplayInfoInternal(false, 30);
  }

  public String getDisplayName() {
    return getDisplayInfoInternal(true, -1);
  }

  private String getDisplayInfoInternal(boolean showPackageInfo, int totalTextLength) {
    final RangeHighlighter highlighter = getHighlighter();
    if (highlighter.isValid() && isValid()) {
      final int lineNumber =
          (highlighter.getDocument().getLineNumber(highlighter.getStartOffset()) + 1);
      String className = getClassName();
      final boolean hasClassInfo = className != null && className.length() > 0;
      final boolean hasMethodInfo = myMethodName != null && myMethodName.length() > 0;
      if (hasClassInfo || hasMethodInfo) {
        final StringBuilder info = StringBuilderSpinAllocator.alloc();
        try {
          boolean isFile = getSourcePosition().getFile().getName().equals(className);
          String packageName = null;
          if (hasClassInfo) {
            final int dotIndex = className.lastIndexOf(".");
            if (dotIndex >= 0 && !isFile) {
              packageName = className.substring(0, dotIndex);
              className = className.substring(dotIndex + 1);
            }

            if (totalTextLength != -1) {
              if (className.length() + (hasMethodInfo ? myMethodName.length() : 0)
                  > totalTextLength + 3) {
                int offset = totalTextLength - (hasMethodInfo ? myMethodName.length() : 0);
                if (offset > 0 && offset < className.length()) {
                  className = className.substring(className.length() - offset);
                  info.append("...");
                }
              }
            }

            info.append(className);
          }
          if (hasMethodInfo) {
            if (isFile) {
              info.append(":");
            } else if (hasClassInfo) {
              info.append(".");
            }
            info.append(myMethodName);
          }
          if (showPackageInfo && packageName != null) {
            info.append(" (").append(packageName).append(")");
          }
          return DebuggerBundle.message(
              "line.breakpoint.display.name.with.class.or.method", lineNumber, info.toString());
        } finally {
          StringBuilderSpinAllocator.dispose(info);
        }
      }
      return DebuggerBundle.message("line.breakpoint.display.name", lineNumber);
    }
    return DebuggerBundle.message("status.breakpoint.invalid");
  }

  private static @Nullable String findMethodName(final PsiFile file, final int offset) {
    if (file instanceof JspFile) {
      return null;
    }
    if (file instanceof PsiClassOwner) {
      return ApplicationManager.getApplication()
          .runReadAction(
              new Computable<String>() {
                public String compute() {
                  final PsiMethod method = DebuggerUtilsEx.findPsiMethod(file, offset);
                  return method != null ? method.getName() + "()" : null;
                }
              });
    }
    return null;
  }

  public String getEventMessage(LocatableEvent event) {
    final Location location = event.location();
    String sourceName = "Unknown Source";
    try {
      sourceName = location.sourceName();
    } catch (AbsentInformationException e) {
      sourceName = getSourcePosition().getFile().getName();
    }

    final boolean printFullTrace = Registry.is("debugger.breakpoint.message.full.trace");

    StringBuilder builder = new StringBuilder();
    if (printFullTrace) {
      builder.append(
          DebuggerBundle.message(
              "status.line.breakpoint.reached.full.trace",
              location.declaringType().name() + "." + location.method().name()));
      try {
        final List<StackFrame> frames = event.thread().frames();
        renderTrace(frames, builder);
      } catch (IncompatibleThreadStateException e) {
        builder.append("Stacktrace not available: ").append(e.getMessage());
      }
    } else {
      builder.append(
          DebuggerBundle.message(
              "status.line.breakpoint.reached",
              location.declaringType().name() + "." + location.method().name(),
              sourceName,
              getLineIndex() + 1));
    }
    return builder.toString();
  }

  private static void renderTrace(List<StackFrame> frames, StringBuilder buffer) {
    for (final StackFrame stackFrame : frames) {
      final Location location = stackFrame.location();
      buffer.append("\n\t  ").append(ThreadDumpAction.renderLocation(location));
    }
  }

  public PsiElement getEvaluationElement() {
    return PositionUtil.getContextElement(getSourcePosition());
  }

  protected static LineBreakpoint create(Project project, Document document, int lineIndex) {
    VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    if (virtualFile == null) {
      return null;
    }

    final RangeHighlighter highlighter = createHighlighter(project, document, lineIndex);
    if (highlighter == null) {
      return null;
    }

    LineBreakpoint breakpoint = new LineBreakpoint(project, highlighter);
    return (LineBreakpoint) breakpoint.init();
  }

  public boolean canMoveTo(SourcePosition position) {
    if (!super.canMoveTo(position)) {
      return false;
    }
    final Document document =
        PsiDocumentManager.getInstance(getProject()).getDocument(position.getFile());
    return canAddLineBreakpoint(myProject, document, position.getLine());
  }

  public static boolean canAddLineBreakpoint(
      Project project, final Document document, final int lineIndex) {
    if (lineIndex < 0 || lineIndex >= document.getLineCount()) {
      return false;
    }
    final BreakpointManager breakpointManager =
        DebuggerManagerEx.getInstanceEx(project).getBreakpointManager();
    final LineBreakpoint breakpointAtLine =
        breakpointManager.findBreakpoint(
            document, document.getLineStartOffset(lineIndex), CATEGORY);
    if (breakpointAtLine != null) {
      // there already exists a line breakpoint at this line
      return false;
    }
    PsiDocumentManager.getInstance(project).commitDocument(document);

    final boolean[] canAdd = new boolean[] {false};
    XDebuggerUtil.getInstance()
        .iterateLine(
            project,
            document,
            lineIndex,
            new Processor<PsiElement>() {
              public boolean process(PsiElement element) {
                if ((element instanceof PsiWhiteSpace)
                    || (PsiTreeUtil.getParentOfType(element, PsiComment.class, false) != null)) {
                  return true;
                }
                PsiElement child = element;
                while (element != null) {

                  final int offset = element.getTextOffset();
                  if (offset >= 0) {
                    if (document.getLineNumber(offset) != lineIndex) {
                      break;
                    }
                  }
                  child = element;
                  element = element.getParent();
                }

                if (child instanceof PsiMethod
                    && child.getTextRange().getEndOffset()
                        >= document.getLineEndOffset(lineIndex)) {
                  PsiCodeBlock body = ((PsiMethod) child).getBody();
                  if (body == null) {
                    canAdd[0] = false;
                  } else {
                    PsiStatement[] statements = body.getStatements();
                    canAdd[0] =
                        statements.length > 0
                            && document.getLineNumber(statements[0].getTextOffset()) == lineIndex;
                  }
                } else {
                  canAdd[0] = true;
                }
                return false;
              }
            });

    return canAdd[0];
  }

  public @Nullable String getMethodName() {
    return myMethodName;
  }
}