org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointRegistry.java
author TasneemS@US-TASNEEMS
Wed, 23 Dec 2009 17:13:18 -0800
changeset 2 e4420d2515f1
permissions -rw-r--r--
Initial version of WRT Debugger.

// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.debug.core.model;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;

import org.chromium.sdk.Breakpoint;
import org.chromium.sdk.Script;

/**
 * A registry of existing breakpoints associated with their script locations. It
 * is used to restore
 */
public class BreakpointRegistry {

  /**
   * Script identifier for a breakpoint location.
   */
  public static class ScriptIdentifier {
    private final String name;

    private final long id;

    private final int startLine;

    private final int endLine;

    public static ScriptIdentifier forScript(Script script) {
      String name = script.getName();
      return new ScriptIdentifier(
          name,
          name != null ? -1 : script.getId(),
          script.getStartLine(),
          script.getEndLine());
    }

    private ScriptIdentifier(String name, long id, int startLine, int endLine) {
      this.name = name;
      this.id = id;
      this.startLine = startLine;
      this.endLine = endLine;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + (int) (id ^ (id >>> 32));
      result = prime * result + ((name == null) ? 0 : name.hashCode());
      result = prime * result + 17 * startLine + 19 * endLine;
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof ScriptIdentifier)) {
        return false;
      }
      ScriptIdentifier that = (ScriptIdentifier) obj;
      if (this.startLine != that.startLine || this.endLine != that.endLine) {
        return false;
      }
      if (name == null) {
        // an unnamed script, only id is known
        return that.name == null && this.id == that.id;
      }
      // a named script
      return this.name.equals(that.name);
    }
  }

  static class BreakpointLocation {
    private final ScriptIdentifier scriptIdentifier;

    private final int line;

    public BreakpointLocation(ScriptIdentifier scriptIdentifier, int line) {
      this.scriptIdentifier = scriptIdentifier;
      this.line = line;
    }

    public ScriptIdentifier getScriptIdentifier() {
      return scriptIdentifier;
    }

    public int getLine() {
      return line;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + line;
      result = prime * result + ((scriptIdentifier == null)
          ? 0
          : scriptIdentifier.hashCode());
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof BreakpointLocation)) {
        return false;
      }
      BreakpointLocation that = (BreakpointLocation) obj;
      return (this.line == that.line && eq(this.scriptIdentifier, that.scriptIdentifier));
    }
  }

  /**
   * A breakpoint accompanied by its line number in the corresponding enclosing
   * resource.
   */
  public static class BreakpointEntry {
    public final Breakpoint breakpoint;

    public final int line;

    private BreakpointEntry(Breakpoint breakpoint, int line) {
      this.breakpoint = breakpoint;
      this.line = line;
    }

    boolean isWithinScriptRange(Script script) {
      return line >= script.getStartLine() && line <= script.getEndLine();
    }

    @Override
    public int hashCode() {
      return 31 * line + 17 * breakpoint.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof BreakpointEntry)) {
        return false;
      }
      BreakpointEntry that = (BreakpointEntry) obj;
      return this.line == that.line && this.breakpoint.equals(that.breakpoint);
    }
  }

  private final Map<ScriptIdentifier, Collection<BreakpointEntry>> scriptIdToBreakpointEntries =
      new HashMap<ScriptIdentifier, Collection<BreakpointEntry>>();

  /**
   * Adds the given line breakpoint.
   *
   * @param script where the breakpoint is set
   * @param line (0-based, like in V8) in the script
   * @param breakpoint
   */
  public void add(Script script, int line, Breakpoint breakpoint) {
    ScriptIdentifier scriptId = ScriptIdentifier.forScript(script);
    Collection<BreakpointEntry> entries = scriptIdToBreakpointEntries.get(scriptId);
    if (entries == null) {
      entries = new HashSet<BreakpointEntry>();
      scriptIdToBreakpointEntries.put(scriptId, entries);
    }
    entries.add(new BreakpointEntry(breakpoint, line));
  }

  /**
   * Gets breakpoint entries for the given script. An empty collection for a
   * {@code null} script.
   *
   * @param script to extract the breakpoints for
   * @return the breakpoints that fall within the given script line range
   */
  public Collection<? extends BreakpointEntry> getBreakpointEntries(Script script) {
    if (script == null) {
      return Collections.emptySet();
    }
    Collection<BreakpointEntry> entries =
        scriptIdToBreakpointEntries.get(ScriptIdentifier.forScript(script));
    if (entries == null) {
      return Collections.emptySet();
    }
    Collection<BreakpointEntry> scriptBreakpoints = new LinkedList<BreakpointEntry>();
    // Linear search should work fairly well for a reasonable number of
    // breakpoints per script
    for (BreakpointEntry entry : entries) {
      if (entry.isWithinScriptRange(script)) {
        scriptBreakpoints.add(entry);
      }
    }
    return scriptBreakpoints;
  }

  /**
   * Removes the given line breakpoint in the given script. Does nothing for a
   * {@code null} script (which may be the case after a navigation + disconnect
   * when the resource referenced by the breakpoint marker is absent.)
   *
   * @param script where the breakpoint is set
   * @param line (0-based, like in V8) in the script
   * @param breakpoint
   */
  public void remove(Script script, int line, Breakpoint breakpoint) {
    if (script == null) {
      return;
    }
    ScriptIdentifier scriptId = ScriptIdentifier.forScript(script);
    Collection<BreakpointEntry> entries = scriptIdToBreakpointEntries.get(scriptId);
    if (entries == null) {
      return;
    }
    if (entries.size() == 1) {
      scriptIdToBreakpointEntries.remove(scriptId);
    } else {
      entries.remove(new BreakpointEntry(breakpoint, line));
    }
  }

  protected static boolean eq(Object left, Object right) {
    return left == right || (left != null && left.equals(right));
  }

}