Added
Link Here
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 2014 Oracle and/or its affiliates. All rights reserved. |
5 |
* |
6 |
* Oracle and Java are registered trademarks of Oracle and/or its affiliates. |
7 |
* Other names may be trademarks of their respective owners. |
8 |
* |
9 |
* The contents of this file are subject to the terms of either the GNU |
10 |
* General Public License Version 2 only ("GPL") or the Common |
11 |
* Development and Distribution License("CDDL") (collectively, the |
12 |
* "License"). You may not use this file except in compliance with the |
13 |
* License. You can obtain a copy of the License at |
14 |
* http://www.netbeans.org/cddl-gplv2.html |
15 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
16 |
* specific language governing permissions and limitations under the |
17 |
* License. When distributing the software, include this License Header |
18 |
* Notice in each file and include the License file at |
19 |
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this |
20 |
* particular file as subject to the "Classpath" exception as provided |
21 |
* by Oracle in the GPL Version 2 section of the License file that |
22 |
* accompanied this code. If applicable, add the following below the |
23 |
* License Header, with the fields enclosed by brackets [] replaced by |
24 |
* your own identifying information: |
25 |
* "Portions Copyrighted [year] [name of copyright owner]" |
26 |
* |
27 |
* If you wish your version of this file to be governed by only the CDDL |
28 |
* or only the GPL Version 2, indicate your decision by adding |
29 |
* "[Contributor] elects to include this software in this distribution |
30 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
31 |
* single choice of license, a recipient has the option to distribute |
32 |
* your version of this file under either the CDDL, the GPL Version 2 or |
33 |
* to extend the choice of license to its licensees as provided above. |
34 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
35 |
* Version 2 license, then the option applies only if the new code is |
36 |
* made subject to such option by the copyright holder. |
37 |
* |
38 |
* Contributor(s): |
39 |
* |
40 |
* Portions Copyrighted 2014 Sun Microsystems, Inc. |
41 |
*/ |
42 |
|
43 |
package org.netbeans.spi.debugger.ui; |
44 |
|
45 |
import java.beans.PropertyChangeListener; |
46 |
import java.beans.PropertyChangeSupport; |
47 |
import java.lang.annotation.ElementType; |
48 |
import java.lang.annotation.Retention; |
49 |
import java.lang.annotation.RetentionPolicy; |
50 |
import java.lang.annotation.Target; |
51 |
import java.lang.ref.WeakReference; |
52 |
import java.util.ArrayList; |
53 |
import java.util.Collections; |
54 |
import java.util.HashMap; |
55 |
import java.util.HashSet; |
56 |
import java.util.LinkedList; |
57 |
import java.util.List; |
58 |
import java.util.Map; |
59 |
import java.util.Set; |
60 |
import java.util.concurrent.CopyOnWriteArraySet; |
61 |
import java.util.prefs.Preferences; |
62 |
import javax.swing.JEditorPane; |
63 |
import javax.swing.SwingUtilities; |
64 |
import org.netbeans.api.debugger.DebuggerEngine; |
65 |
import org.netbeans.api.debugger.Properties; |
66 |
import org.netbeans.modules.debugger.ui.eval.CodeEvaluatorUI; |
67 |
import org.netbeans.modules.debugger.ui.views.VariablesViewButtons; |
68 |
import org.netbeans.spi.debugger.ContextProvider; |
69 |
import org.openide.util.NbPreferences; |
70 |
import org.openide.windows.TopComponent; |
71 |
import org.openide.windows.WindowManager; |
72 |
|
73 |
/** |
74 |
* Code evaluator UI. Use this to access and manage a component for code |
75 |
* evaluations and handle it's result. |
76 |
* |
77 |
* @author Martin Entlicher |
78 |
* @since 2.48 |
79 |
*/ |
80 |
public final class CodeEvaluator { |
81 |
|
82 |
private static final CodeEvaluator INSTANCE = new CodeEvaluator(); |
83 |
|
84 |
private CodeEvaluator() { |
85 |
} |
86 |
|
87 |
/** |
88 |
* Get the default instance of the code evaluator. |
89 |
* @return The code evaluator instance. |
90 |
*/ |
91 |
public static CodeEvaluator getDefault() { |
92 |
return INSTANCE; |
93 |
} |
94 |
|
95 |
/** |
96 |
* Open the evaluator UI. |
97 |
*/ |
98 |
public void open() { |
99 |
CodeEvaluatorUI.openEvaluator(); |
100 |
} |
101 |
|
102 |
/** |
103 |
* Set an expression to the evaluator UI. |
104 |
* @param expression The expression to set to the evaluator UI. |
105 |
*/ |
106 |
public void setExpression(String expression) { |
107 |
CodeEvaluatorUI.getInstance().pasteExpression(expression); |
108 |
} |
109 |
|
110 |
/** |
111 |
* Request focus to the evaluator UI. |
112 |
*/ |
113 |
public void requestFocus() { |
114 |
CodeEvaluatorUI.getInstance().requestFocusInWindow(); |
115 |
} |
116 |
|
117 |
/** |
118 |
* Service handling evaluations in the code evaluator. |
119 |
* Register an implementation of this class via {@link Registration} annotation. |
120 |
*/ |
121 |
public static abstract class EvaluatorService { |
122 |
|
123 |
/** |
124 |
* Property name fired in order to refresh the evaluate button state. |
125 |
*/ |
126 |
public static final String PROP_CAN_EVALUATE = "canEvaluate"; // NOI18N |
127 |
/** |
128 |
* Property name fired in order to refresh the list of expressions history. |
129 |
*/ |
130 |
public static final String PROP_EXPRESSIONS_HISTORY = "expressionsHistory"; // NOI18N |
131 |
|
132 |
private final PropertyChangeSupport pchs = new PropertyChangeSupport(this); |
133 |
|
134 |
/** |
135 |
* Perform setup of the editor pane of the code evaluator. |
136 |
* Typically perform binding for code completion. |
137 |
* @param editorPane |
138 |
* @param setUpCallback call this back when the setup is finished. |
139 |
* As the setup may require asynchronous calls, |
140 |
* this is necessary to notify the infrastructure |
141 |
* that the code editor is set up. |
142 |
*/ |
143 |
public abstract void setupContext(JEditorPane editorPane, Runnable setUpCallback); |
144 |
|
145 |
/** |
146 |
* Provide status whether the service is ready to evaluate code now. |
147 |
* Fire {@link #PROP_CAN_EVALUATE} when this status changes. |
148 |
* @return <code>true</code> when code evaluation is possible (e.g. the debugger is suspended), |
149 |
* <code>false</code> otherwise. |
150 |
*/ |
151 |
public abstract boolean canEvaluate(); |
152 |
|
153 |
/** |
154 |
* Evaluate the given expression. |
155 |
* The evaluation typically should be performed asynchronously, and the |
156 |
* result is then set to {@link Result} or any other custom result handler. |
157 |
* @param expression The expression to evaluate. |
158 |
*/ |
159 |
public abstract void evaluate(String expression); |
160 |
|
161 |
/** |
162 |
* Get the historical evaluated expressions. Items provided by this list |
163 |
* are accessible from the code evaluator UI. |
164 |
* @return The list of evaluated expressions. |
165 |
*/ |
166 |
public abstract List<String> getExpressionsHistory(); |
167 |
|
168 |
/** |
169 |
* Fire a property change event. |
170 |
* @param propertyName The property name, one of PROP_* constants. |
171 |
* @param oldValue An old value |
172 |
* @param newValue A new value |
173 |
*/ |
174 |
protected final void firePropertyChange(String propertyName, Object oldValue, Object newValue) { |
175 |
pchs.firePropertyChange(propertyName, oldValue, newValue); |
176 |
} |
177 |
|
178 |
/** |
179 |
* Add a property change listener to be notified about property change events. |
180 |
* @param l the property change listener |
181 |
*/ |
182 |
public final void addPropertyChangeListener(PropertyChangeListener l) { |
183 |
pchs.addPropertyChangeListener(l); |
184 |
} |
185 |
|
186 |
/** |
187 |
* Remove a property change listener. |
188 |
* @param l the property change listener |
189 |
*/ |
190 |
public final void removePropertyChangeListener(PropertyChangeListener l) { |
191 |
pchs.removePropertyChangeListener(l); |
192 |
} |
193 |
|
194 |
/** |
195 |
* Declarative registration of a EvaluatorService implementation. |
196 |
* By marking the implementation class with this annotation, |
197 |
* you automatically register that implementation for use by the debugger evaluator. |
198 |
* The class must be public and have a public constructor which takes |
199 |
* no arguments or takes {@link ContextProvider} as an argument. |
200 |
* |
201 |
* @author Martin Entlicher |
202 |
*/ |
203 |
@Retention(RetentionPolicy.SOURCE) |
204 |
@Target({ElementType.TYPE}) |
205 |
public @interface Registration { |
206 |
/** |
207 |
* The path to register this implementation in. |
208 |
* Usually the session or engine ID. |
209 |
*/ |
210 |
String path(); |
211 |
|
212 |
/** |
213 |
* An optional position in which to register this service relative to others. |
214 |
* Lower-numbered services are returned in the lookup result first. |
215 |
* Services with no specified position are returned last. |
216 |
*/ |
217 |
int position() default Integer.MAX_VALUE; |
218 |
|
219 |
} |
220 |
} |
221 |
|
222 |
/** |
223 |
* Default implementation of expressions history persistence. |
224 |
* Can be used by {@link EvaluatorService#getExpressionsHistory()}. |
225 |
*/ |
226 |
public static final class DefaultExpressionsHistoryPersistence { |
227 |
|
228 |
private static final int NUM_HISTORY_ITEMS = 20; |
229 |
|
230 |
private final String engineName; |
231 |
private ArrayList<String> editItemsList; |
232 |
private Set<String> editItemsSet; |
233 |
|
234 |
private DefaultExpressionsHistoryPersistence(String engineName) { |
235 |
this.engineName = engineName; |
236 |
getExpressions(); |
237 |
} |
238 |
|
239 |
/** |
240 |
* Create a new instance of default expressions history persistence. |
241 |
* @param engineName The name of the persistence storage. Usually the engine name to be unique. |
242 |
* @return A new instance of default expressions history persistence. |
243 |
*/ |
244 |
public static DefaultExpressionsHistoryPersistence create(String engineName) { |
245 |
return new DefaultExpressionsHistoryPersistence(engineName); |
246 |
} |
247 |
|
248 |
/** |
249 |
* Get the list of stored expressions. |
250 |
* @return the list of stored expressions. |
251 |
*/ |
252 |
public synchronized List<String> getExpressions() { |
253 |
if (editItemsList == null) { |
254 |
editItemsList = (ArrayList<String>) |
255 |
Properties.getDefault().getProperties(engineName). |
256 |
getCollection("EvaluatorItems", new ArrayList()); // NOI18N |
257 |
editItemsSet = new HashSet<String>(editItemsList); |
258 |
} |
259 |
return editItemsList; |
260 |
} |
261 |
|
262 |
/** |
263 |
* Add a new expression to the history. |
264 |
* @param expression a new expression to store. |
265 |
*/ |
266 |
public synchronized void addExpression(String expression) { |
267 |
expression = expression.trim(); |
268 |
//getExpressions(); // To asure that we're initialized. |
269 |
if (editItemsSet.contains(expression)) { |
270 |
editItemsList.remove(expression); |
271 |
editItemsList.add(0, expression); |
272 |
} else { |
273 |
editItemsList.add(0, expression); |
274 |
editItemsSet.add(expression); |
275 |
if (editItemsList.size() > NUM_HISTORY_ITEMS) { |
276 |
String removed = editItemsList.remove(editItemsList.size() - 1); |
277 |
editItemsSet.remove(removed); |
278 |
} |
279 |
} |
280 |
storeEditItems(editItemsList); |
281 |
} |
282 |
|
283 |
private void storeEditItems(ArrayList<String> items) { |
284 |
Properties.getDefault().getProperties(engineName). |
285 |
setCollection("EvaluatorItems", items); // NOI18N |
286 |
} |
287 |
} |
288 |
|
289 |
/** |
290 |
* A helper class managing the evaluator result. Use it when "resultsView" |
291 |
* is used to visualize the result of evaluation. |
292 |
* @param <R> The type of the result object |
293 |
* @param <H> The type of the result history item ({@link DefaultHistoryItem} can be used). |
294 |
*/ |
295 |
public static final class Result<R, H> { |
296 |
|
297 |
// Do not use WeakSet, because the value references to the key. |
298 |
private static final Map<Integer, List<WeakReference<Result>>> ENGINE_HASH_MAP = new HashMap<Integer, List<WeakReference<Result>>>(); |
299 |
|
300 |
private final Preferences preferences = NbPreferences.forModule(ContextProvider.class).node(VariablesViewButtons.PREFERENCES_NAME); |
301 |
private final DebuggerEngine engine; |
302 |
|
303 |
private TopComponent resultView; |
304 |
private volatile String expression; |
305 |
private volatile R result; |
306 |
private final List<H> historyItems = new ArrayList<H>(); |
307 |
private final List<H> historyItemsRO = Collections.unmodifiableList(historyItems); |
308 |
private H lastHistoryItem; |
309 |
private int maxHistoryItems = 100; |
310 |
private final Set<Listener<R>> listeners = new CopyOnWriteArraySet<Listener<R>>(); |
311 |
|
312 |
private Result(DebuggerEngine engine) { |
313 |
this.engine = engine; |
314 |
} |
315 |
|
316 |
/** |
317 |
* Get an instance of Result for the given {@link DebuggerEngine}. |
318 |
* A new instance is created if there is not one available, an existing |
319 |
* instance is returned otherwise. |
320 |
* @param <R> The type of the result object |
321 |
* @param <H> The type of the result history item |
322 |
* @param engine The debugger engine |
323 |
* @return an instance of Result container. |
324 |
*/ |
325 |
public static <R, H> Result<R, H> get(DebuggerEngine engine) { |
326 |
int hc = engine.hashCode(); |
327 |
synchronized (ENGINE_HASH_MAP) { |
328 |
List<WeakReference<Result>> elist = ENGINE_HASH_MAP.get(hc); |
329 |
if (elist == null) { |
330 |
elist = new LinkedList<WeakReference<Result>>(); |
331 |
ENGINE_HASH_MAP.put(hc, elist); |
332 |
} |
333 |
//Result r = null; |
334 |
for (int i = 0; i < elist.size(); i++) { |
335 |
WeakReference<Result> wr = elist.get(i); |
336 |
Result r = wr.get(); |
337 |
if (r == null) { |
338 |
elist.remove(wr); |
339 |
i--; |
340 |
continue; |
341 |
} |
342 |
if (engine == r.engine) { |
343 |
return r; |
344 |
} |
345 |
} |
346 |
Result<R, H> r = new Result(engine); |
347 |
elist.add(new WeakReference<Result>(r)); |
348 |
return r; |
349 |
} |
350 |
} |
351 |
|
352 |
/** |
353 |
* Set the maximum number of result history items being held. |
354 |
* By default this is 100. |
355 |
* @param maxHistoryItems maximum number of history items |
356 |
*/ |
357 |
public void setMaxHistoryItems(int maxHistoryItems) { |
358 |
if (maxHistoryItems < 0) { |
359 |
throw new IllegalArgumentException(Integer.toString(maxHistoryItems)); |
360 |
} |
361 |
this.maxHistoryItems = maxHistoryItems; |
362 |
} |
363 |
|
364 |
/** |
365 |
* Set the current expression, it's result and a history item, that is |
366 |
* added to the list of history items upon the next evaluation. |
367 |
* @param expression The evaluated expression |
368 |
* @param result The result of that expression |
369 |
* @param historyItem A history representation of the result |
370 |
*/ |
371 |
public void setAndOpen(final String expression, final R result, final H historyItem) { |
372 |
this.expression = expression; |
373 |
this.result = result; |
374 |
if (lastHistoryItem != null) { |
375 |
synchronized (historyItems) { |
376 |
historyItems.add(0, lastHistoryItem); |
377 |
while (historyItems.size() > maxHistoryItems) { |
378 |
historyItems.remove(historyItems.size() - 1); |
379 |
} |
380 |
} |
381 |
} |
382 |
lastHistoryItem = historyItem; |
383 |
if (result == null) { |
384 |
fireResultChange(result); |
385 |
return ; |
386 |
} |
387 |
SwingUtilities.invokeLater(new Runnable() { |
388 |
public void run() { |
389 |
boolean isMinimized = false; |
390 |
if (preferences.getBoolean("show_evaluator_result", true)) { |
391 |
TopComponent view = WindowManager.getDefault().findTopComponent("localsView"); // NOI18N [TODO] |
392 |
view.open(); |
393 |
isMinimized = WindowManager.getDefault().isTopComponentMinimized(view); |
394 |
view.requestActive(); |
395 |
} else { |
396 |
if (resultView == null) { |
397 |
resultView = getResultViewInstance(); |
398 |
} |
399 |
if (result != null && resultView != null) { |
400 |
resultView.open(); |
401 |
isMinimized = WindowManager.getDefault().isTopComponentMinimized(resultView); |
402 |
resultView.requestActive(); |
403 |
} |
404 |
} |
405 |
if (!isMinimized) { |
406 |
CodeEvaluatorUI.getInstance().requestActive(); |
407 |
} |
408 |
fireResultChange(result); |
409 |
} |
410 |
}); |
411 |
} |
412 |
|
413 |
/** |
414 |
* Get the current expression. |
415 |
* @return the expression set by {@link #setAndOpen(java.lang.String, java.lang.Object, java.lang.Object)} |
416 |
*/ |
417 |
public String getExpression() { |
418 |
return expression; |
419 |
} |
420 |
|
421 |
/** |
422 |
* Get the current result. |
423 |
* @return the result set by {@link #setAndOpen(java.lang.String, java.lang.Object, java.lang.Object)} |
424 |
*/ |
425 |
public R getResult() { |
426 |
return result; |
427 |
} |
428 |
|
429 |
/** |
430 |
* Get the history items. |
431 |
* @return the history items. |
432 |
*/ |
433 |
public List<H> getHistoryItems() { |
434 |
return historyItemsRO; |
435 |
} |
436 |
|
437 |
private synchronized TopComponent getResultViewInstance() { |
438 |
/** unique ID of <code>TopComponent</code> (singleton) */ |
439 |
TopComponent instance = WindowManager.getDefault().findTopComponent("resultsView"); // NOI18N [TODO] |
440 |
// Can be null |
441 |
return instance; |
442 |
} |
443 |
|
444 |
private void fireResultChange(R result) { |
445 |
for (Listener<R> l : listeners) { |
446 |
l.resultChanged(result); |
447 |
} |
448 |
} |
449 |
|
450 |
/** |
451 |
* Add a result change listener. |
452 |
* @param l result change listener |
453 |
*/ |
454 |
public void addListener(Listener<R> l) { |
455 |
listeners.add(l); |
456 |
} |
457 |
|
458 |
/** |
459 |
* Remove a result change listener. |
460 |
* @param l result change listener |
461 |
*/ |
462 |
public void removeListener(Listener<R> l) { |
463 |
listeners.remove(l); |
464 |
} |
465 |
|
466 |
/** |
467 |
* Listener that is notified when result changes. |
468 |
* @param <R> the type of result object. |
469 |
*/ |
470 |
public interface Listener<R> { |
471 |
|
472 |
/** |
473 |
* Notify that the result object has changed. |
474 |
* @param o a new result object. |
475 |
*/ |
476 |
void resultChanged(R o); |
477 |
} |
478 |
|
479 |
/** |
480 |
* A default implementation of a history item. A utility class that handles |
481 |
* typical usage. |
482 |
*/ |
483 |
public static final class DefaultHistoryItem { |
484 |
|
485 |
private final String expression; |
486 |
private final String type; |
487 |
private final String value; |
488 |
private final String toStringValue; |
489 |
private final String tooltip; |
490 |
|
491 |
/** |
492 |
* Create a new history item. |
493 |
* @param expression The evaluated expression |
494 |
* @param type type of the result |
495 |
* @param value value of the result |
496 |
* @param toStringValue toString representation of the result |
497 |
*/ |
498 |
public DefaultHistoryItem(String expression, String type, String value, String toStringValue) { |
499 |
this.expression = expression; |
500 |
this.type = type; |
501 |
this.value = value; |
502 |
this.toStringValue = toStringValue; |
503 |
StringBuffer buf = new StringBuffer(); |
504 |
buf.append("<html>"); |
505 |
String text = expression.replaceAll ("&", "&"); |
506 |
text = text.replaceAll ("<", "<"); |
507 |
text = text.replaceAll (">", ">"); |
508 |
text = text.replaceAll ("\n", "<br/>"); |
509 |
text = text.replaceAll ("\r", ""); |
510 |
buf.append(text); |
511 |
buf.append("</html>"); |
512 |
this.tooltip = buf.toString(); |
513 |
} |
514 |
|
515 |
/** |
516 |
* Get the evaluated expression. |
517 |
* @return the expression |
518 |
*/ |
519 |
public String getExpression() { |
520 |
return expression; |
521 |
} |
522 |
|
523 |
/** |
524 |
* Get the type of the result. |
525 |
* @return the type of the result |
526 |
*/ |
527 |
public String getType() { |
528 |
return type; |
529 |
} |
530 |
|
531 |
/** |
532 |
* Get the value of the result. |
533 |
* @return the value of the result |
534 |
*/ |
535 |
public String getValue() { |
536 |
return value; |
537 |
} |
538 |
|
539 |
/** |
540 |
* Get toString representation of the result. |
541 |
* @return toString representation of the result |
542 |
*/ |
543 |
public String getToStringValue() { |
544 |
return toStringValue; |
545 |
} |
546 |
|
547 |
/** |
548 |
* Get tooltip displayed upon the history item. |
549 |
* @return the tooltip |
550 |
*/ |
551 |
public String getTooltip() { |
552 |
return tooltip; |
553 |
} |
554 |
|
555 |
} |
556 |
} |
557 |
} |