This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 248626
Collapse All | Expand All

(-)a/spi.debugger.ui/apichanges.xml (+18 lines)
Lines 211-216 Link Here
211
        <issue number="193872"/>
211
        <issue number="193872"/>
212
    </change>
212
    </change>
213
213
214
    <change id="CodeEvaluator">
215
        <api name="DebuggerCoreSPI"/>
216
        <summary><code>CodeEvaluator</code> class added.</summary>
217
        <version major="2" minor="48"/>
218
        <date day="28" month="11" year="2014"/>
219
        <author login="mentlicher"/>
220
        <compatibility binary="compatible" source="compatible" addition="yes" semantic="compatible"/>
221
        <description>
222
            <p>
223
                <code>CodeEvaluator</code> class introduced
224
                to access and manage a component for code evaluations
225
                and handle it's result.
226
            </p>
227
        </description>
228
        <class package="org.netbeans.spi.debugger.ui" name="CodeEvaluator"/>
229
        <issue number="248626"/>
230
    </change>
231
214
</changes>
232
</changes>
215
233
216
  <!-- Now the surrounding HTML text and document structure: -->
234
  <!-- Now the surrounding HTML text and document structure: -->
(-)a/spi.debugger.ui/manifest.mf (-1 / +1 lines)
Lines 2-7 Link Here
2
OpenIDE-Module: org.netbeans.spi.debugger.ui/1
2
OpenIDE-Module: org.netbeans.spi.debugger.ui/1
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/debugger/ui/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/debugger/ui/Bundle.properties
4
OpenIDE-Module-Layer: org/netbeans/modules/debugger/resources/mf-layer.xml
4
OpenIDE-Module-Layer: org/netbeans/modules/debugger/resources/mf-layer.xml
5
OpenIDE-Module-Specification-Version: 2.47
5
OpenIDE-Module-Specification-Version: 2.48
6
OpenIDE-Module-Provides: org.netbeans.spi.debugger.ui
6
OpenIDE-Module-Provides: org.netbeans.spi.debugger.ui
7
OpenIDE-Module-Install: org/netbeans/modules/debugger/ui/DebuggerModule.class
7
OpenIDE-Module-Install: org/netbeans/modules/debugger/ui/DebuggerModule.class
(-)549fcf920254 (+557 lines)
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 ("&", "&amp;");
506
                text = text.replaceAll ("<", "&lt;");
507
                text = text.replaceAll (">", "&gt;");
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
}

Return to bug 248626