1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
|
2 // Use of this source code is governed by a BSD-style license that can be |
|
3 // found in the LICENSE file. |
|
4 |
|
5 package org.chromium.debug.core.model; |
|
6 |
|
7 import java.util.Collection; |
|
8 import java.util.Collections; |
|
9 import java.util.HashMap; |
|
10 import java.util.HashSet; |
|
11 import java.util.LinkedList; |
|
12 import java.util.Map; |
|
13 |
|
14 import org.chromium.sdk.Breakpoint; |
|
15 import org.chromium.sdk.Script; |
|
16 |
|
17 /** |
|
18 * A registry of existing breakpoints associated with their script locations. It |
|
19 * is used to restore |
|
20 */ |
|
21 public class BreakpointRegistry { |
|
22 |
|
23 /** |
|
24 * Script identifier for a breakpoint location. |
|
25 */ |
|
26 public static class ScriptIdentifier { |
|
27 private final String name; |
|
28 |
|
29 private final long id; |
|
30 |
|
31 private final int startLine; |
|
32 |
|
33 private final int endLine; |
|
34 |
|
35 public static ScriptIdentifier forScript(Script script) { |
|
36 String name = script.getName(); |
|
37 return new ScriptIdentifier( |
|
38 name, |
|
39 name != null ? -1 : script.getId(), |
|
40 script.getStartLine(), |
|
41 script.getEndLine()); |
|
42 } |
|
43 |
|
44 private ScriptIdentifier(String name, long id, int startLine, int endLine) { |
|
45 this.name = name; |
|
46 this.id = id; |
|
47 this.startLine = startLine; |
|
48 this.endLine = endLine; |
|
49 } |
|
50 |
|
51 @Override |
|
52 public int hashCode() { |
|
53 final int prime = 31; |
|
54 int result = 1; |
|
55 result = prime * result + (int) (id ^ (id >>> 32)); |
|
56 result = prime * result + ((name == null) ? 0 : name.hashCode()); |
|
57 result = prime * result + 17 * startLine + 19 * endLine; |
|
58 return result; |
|
59 } |
|
60 |
|
61 @Override |
|
62 public boolean equals(Object obj) { |
|
63 if (!(obj instanceof ScriptIdentifier)) { |
|
64 return false; |
|
65 } |
|
66 ScriptIdentifier that = (ScriptIdentifier) obj; |
|
67 if (this.startLine != that.startLine || this.endLine != that.endLine) { |
|
68 return false; |
|
69 } |
|
70 if (name == null) { |
|
71 // an unnamed script, only id is known |
|
72 return that.name == null && this.id == that.id; |
|
73 } |
|
74 // a named script |
|
75 return this.name.equals(that.name); |
|
76 } |
|
77 } |
|
78 |
|
79 static class BreakpointLocation { |
|
80 private final ScriptIdentifier scriptIdentifier; |
|
81 |
|
82 private final int line; |
|
83 |
|
84 public BreakpointLocation(ScriptIdentifier scriptIdentifier, int line) { |
|
85 this.scriptIdentifier = scriptIdentifier; |
|
86 this.line = line; |
|
87 } |
|
88 |
|
89 public ScriptIdentifier getScriptIdentifier() { |
|
90 return scriptIdentifier; |
|
91 } |
|
92 |
|
93 public int getLine() { |
|
94 return line; |
|
95 } |
|
96 |
|
97 @Override |
|
98 public int hashCode() { |
|
99 final int prime = 31; |
|
100 int result = 1; |
|
101 result = prime * result + line; |
|
102 result = prime * result + ((scriptIdentifier == null) |
|
103 ? 0 |
|
104 : scriptIdentifier.hashCode()); |
|
105 return result; |
|
106 } |
|
107 |
|
108 @Override |
|
109 public boolean equals(Object obj) { |
|
110 if (!(obj instanceof BreakpointLocation)) { |
|
111 return false; |
|
112 } |
|
113 BreakpointLocation that = (BreakpointLocation) obj; |
|
114 return (this.line == that.line && eq(this.scriptIdentifier, that.scriptIdentifier)); |
|
115 } |
|
116 } |
|
117 |
|
118 /** |
|
119 * A breakpoint accompanied by its line number in the corresponding enclosing |
|
120 * resource. |
|
121 */ |
|
122 public static class BreakpointEntry { |
|
123 public final Breakpoint breakpoint; |
|
124 |
|
125 public final int line; |
|
126 |
|
127 private BreakpointEntry(Breakpoint breakpoint, int line) { |
|
128 this.breakpoint = breakpoint; |
|
129 this.line = line; |
|
130 } |
|
131 |
|
132 boolean isWithinScriptRange(Script script) { |
|
133 return line >= script.getStartLine() && line <= script.getEndLine(); |
|
134 } |
|
135 |
|
136 @Override |
|
137 public int hashCode() { |
|
138 return 31 * line + 17 * breakpoint.hashCode(); |
|
139 } |
|
140 |
|
141 @Override |
|
142 public boolean equals(Object obj) { |
|
143 if (!(obj instanceof BreakpointEntry)) { |
|
144 return false; |
|
145 } |
|
146 BreakpointEntry that = (BreakpointEntry) obj; |
|
147 return this.line == that.line && this.breakpoint.equals(that.breakpoint); |
|
148 } |
|
149 } |
|
150 |
|
151 private final Map<ScriptIdentifier, Collection<BreakpointEntry>> scriptIdToBreakpointEntries = |
|
152 new HashMap<ScriptIdentifier, Collection<BreakpointEntry>>(); |
|
153 |
|
154 /** |
|
155 * Adds the given line breakpoint. |
|
156 * |
|
157 * @param script where the breakpoint is set |
|
158 * @param line (0-based, like in V8) in the script |
|
159 * @param breakpoint |
|
160 */ |
|
161 public void add(Script script, int line, Breakpoint breakpoint) { |
|
162 ScriptIdentifier scriptId = ScriptIdentifier.forScript(script); |
|
163 Collection<BreakpointEntry> entries = scriptIdToBreakpointEntries.get(scriptId); |
|
164 if (entries == null) { |
|
165 entries = new HashSet<BreakpointEntry>(); |
|
166 scriptIdToBreakpointEntries.put(scriptId, entries); |
|
167 } |
|
168 entries.add(new BreakpointEntry(breakpoint, line)); |
|
169 } |
|
170 |
|
171 /** |
|
172 * Gets breakpoint entries for the given script. An empty collection for a |
|
173 * {@code null} script. |
|
174 * |
|
175 * @param script to extract the breakpoints for |
|
176 * @return the breakpoints that fall within the given script line range |
|
177 */ |
|
178 public Collection<? extends BreakpointEntry> getBreakpointEntries(Script script) { |
|
179 if (script == null) { |
|
180 return Collections.emptySet(); |
|
181 } |
|
182 Collection<BreakpointEntry> entries = |
|
183 scriptIdToBreakpointEntries.get(ScriptIdentifier.forScript(script)); |
|
184 if (entries == null) { |
|
185 return Collections.emptySet(); |
|
186 } |
|
187 Collection<BreakpointEntry> scriptBreakpoints = new LinkedList<BreakpointEntry>(); |
|
188 // Linear search should work fairly well for a reasonable number of |
|
189 // breakpoints per script |
|
190 for (BreakpointEntry entry : entries) { |
|
191 if (entry.isWithinScriptRange(script)) { |
|
192 scriptBreakpoints.add(entry); |
|
193 } |
|
194 } |
|
195 return scriptBreakpoints; |
|
196 } |
|
197 |
|
198 /** |
|
199 * Removes the given line breakpoint in the given script. Does nothing for a |
|
200 * {@code null} script (which may be the case after a navigation + disconnect |
|
201 * when the resource referenced by the breakpoint marker is absent.) |
|
202 * |
|
203 * @param script where the breakpoint is set |
|
204 * @param line (0-based, like in V8) in the script |
|
205 * @param breakpoint |
|
206 */ |
|
207 public void remove(Script script, int line, Breakpoint breakpoint) { |
|
208 if (script == null) { |
|
209 return; |
|
210 } |
|
211 ScriptIdentifier scriptId = ScriptIdentifier.forScript(script); |
|
212 Collection<BreakpointEntry> entries = scriptIdToBreakpointEntries.get(scriptId); |
|
213 if (entries == null) { |
|
214 return; |
|
215 } |
|
216 if (entries.size() == 1) { |
|
217 scriptIdToBreakpointEntries.remove(scriptId); |
|
218 } else { |
|
219 entries.remove(new BreakpointEntry(breakpoint, line)); |
|
220 } |
|
221 } |
|
222 |
|
223 protected static boolean eq(Object left, Object right) { |
|
224 return left == right || (left != null && left.equals(right)); |
|
225 } |
|
226 |
|
227 } |
|