diff --git a/api.debugger/apichanges.xml b/api.debugger/apichanges.xml --- a/api.debugger/apichanges.xml +++ b/api.debugger/apichanges.xml @@ -75,6 +75,22 @@ + + + API for pinned Watches + + + + + + A Watch.Pin base interface introduced as a basis + for specific platform-dependent and location-dependent implementations. + DebuggerManager.createPinnedwatch() introduced. + + + + + API for changing enabled state of a Watch diff --git a/api.debugger/manifest.mf b/api.debugger/manifest.mf --- a/api.debugger/manifest.mf +++ b/api.debugger/manifest.mf @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.api.debugger/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/api/debugger/Bundle.properties -OpenIDE-Module-Specification-Version: 1.53 +OpenIDE-Module-Specification-Version: 1.54 OpenIDE-Module-Layer: org/netbeans/api/debugger/layer.xml diff --git a/api.debugger/src/org/netbeans/api/debugger/DebuggerManager.java b/api.debugger/src/org/netbeans/api/debugger/DebuggerManager.java --- a/api.debugger/src/org/netbeans/api/debugger/DebuggerManager.java +++ b/api.debugger/src/org/netbeans/api/debugger/DebuggerManager.java @@ -665,6 +665,27 @@ } return w; } + + /** + * Create a watch pinned at the specified pin location. + * @param expr expression to watch for (the format is the responsibility + * of the debugger plug-in implementation, but it is typically + * a variable name). + * @param pin A pin where the watch should be pinned at. + * @return the new watch + * @since 1.54 + */ + public Watch createPinnedWatch(String expr, Watch.Pin pin) { + Watch w = new Watch (expr, pin); + if (Boolean.TRUE.equals(watchesInitializing.get())) { + watches.addElement (w); + } else { + initWatches (); + watches.addElement (w); + fireWatchCreated (w); + } + return w; + } /** * Gets all shared watches in the system. diff --git a/api.debugger/src/org/netbeans/api/debugger/Watch.java b/api.debugger/src/org/netbeans/api/debugger/Watch.java --- a/api.debugger/src/org/netbeans/api/debugger/Watch.java +++ b/api.debugger/src/org/netbeans/api/debugger/Watch.java @@ -47,7 +47,6 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; - /** * Abstract definition of watch. Each watch is created for * one String which contains the name of variable or some expression. @@ -68,9 +67,15 @@ private boolean enabled = true; private PropertyChangeSupport pcs; + private final Pin pin; Watch (String expr) { + this(expr, null); + } + + Watch (String expr, Pin pin) { this.expression = expr; + this.pin = pin; pcs = new PropertyChangeSupport (this); } @@ -121,7 +126,16 @@ } pcs.firePropertyChange (PROP_EXPRESSION, old, expression); } - + + /** + * Get a pin location, where the watch is pinned at, if any. + * @return The watch pin, or null. + * @since 1.54 + */ + public Pin getPin() { + return pin; + } + /** * Remove the watch from the list of all watches in the system. */ @@ -147,5 +161,16 @@ public void removePropertyChangeListener (PropertyChangeListener l) { pcs.removePropertyChangeListener (l); } + + /** + * A base interface for a watch pin location. Implemented by specific + * platform-dependent and location-dependent implementation. + * See org.netbeans.spi.debugger.ui.EditorPin for the NetBeans + * editor pin implementation. + * @since 1.54 + */ + public static interface Pin { + + } } diff --git a/spi.debugger.ui/manifest.mf b/spi.debugger.ui/manifest.mf --- a/spi.debugger.ui/manifest.mf +++ b/spi.debugger.ui/manifest.mf @@ -2,6 +2,6 @@ OpenIDE-Module: org.netbeans.spi.debugger.ui/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/debugger/ui/Bundle.properties OpenIDE-Module-Layer: org/netbeans/modules/debugger/resources/mf-layer.xml -OpenIDE-Module-Specification-Version: 2.52 +OpenIDE-Module-Specification-Version: 2.53 OpenIDE-Module-Provides: org.netbeans.spi.debugger.ui OpenIDE-Module-Install: org/netbeans/modules/debugger/ui/DebuggerModule.class diff --git a/spi.debugger.ui/nbproject/project.properties b/spi.debugger.ui/nbproject/project.properties --- a/spi.debugger.ui/nbproject/project.properties +++ b/spi.debugger.ui/nbproject/project.properties @@ -42,7 +42,7 @@ is.autoload=true javac.compilerargs=-Xlint -Xlint:-serial -javac.source=1.7 +javac.source=1.8 javadoc.arch=${basedir}/arch.xml test.config.stableBTD.includes=**/*Test.class diff --git a/spi.debugger.ui/nbproject/project.xml b/spi.debugger.ui/nbproject/project.xml --- a/spi.debugger.ui/nbproject/project.xml +++ b/spi.debugger.ui/nbproject/project.xml @@ -68,6 +68,14 @@ + org.netbeans.modules.editor.document + + + + 1.6 + + + org.netbeans.modules.editor.fold diff --git a/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/Bundle.properties b/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/Bundle.properties --- a/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/Bundle.properties +++ b/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/Bundle.properties @@ -118,3 +118,4 @@ HINT_CURRENT_EXP_LINE_DCBP=Current Program Counter In Expression + Disabled Conditional Breakpoint HINT_CURRENT_EXP=Current Expression +HINT_WATCH_PIN=Watch diff --git a/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/WatchPin.xml b/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/WatchPin.xml new file mode 100644 --- /dev/null +++ b/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/WatchPin.xml @@ -0,0 +1,55 @@ + + + + + \ No newline at end of file diff --git a/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/mf-layer.xml b/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/mf-layer.xml --- a/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/mf-layer.xml +++ b/spi.debugger.ui/src/org/netbeans/modules/debugger/resources/mf-layer.xml @@ -542,6 +542,7 @@ + diff --git a/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/EditorPin.java b/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/EditorPin.java new file mode 100644 --- /dev/null +++ b/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/EditorPin.java @@ -0,0 +1,177 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2016 Sun Microsystems, Inc. + */ +package org.netbeans.spi.debugger.ui; + +import java.awt.Point; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.Objects; +import org.netbeans.api.debugger.Watch; +import org.openide.filesystems.FileObject; + +/** + * Implementation of watch pin in editor. + * + * @author Ralph Benjamin Ruijs + * @since 2.53 + */ +public final class EditorPin implements Watch.Pin { + + /** + * Line property, fired when a line change. + */ + public static final String PROP_LINE = "line"; + /** + * Location property, fired when a location change. + */ + public static final String PROP_LOCATION = "location"; + /** + * Comment property, fired when a comment change. + */ + public static final String PROP_COMMENT = "comment"; + + private final PropertyChangeSupport pchs = new PropertyChangeSupport(this); + + private final FileObject file; + private volatile int line; + private volatile Point location; + private volatile String comment; + private String vpId; + + /** + * Create a new pin location in editor. + * @param file The editor's file + * @param line The line location of the pin + * @param location Coordinates of the pin location in editor + */ + public EditorPin(FileObject file, int line, Point location) { + this.file = file; + this.line = line; + this.location = location; + } + + /** + * Get the line location of the pin. + * @return The line. + */ + public int getLine() { + return line; + } + + /** + * Get the file object associated with the editor containing the pin. + * @return The file object. + */ + public FileObject getFile() { + return file; + } + + /** + * Location of the pin in editor. + * @return The location point. + */ + public Point getLocation() { + return location; + } + + /** + * Move the pin to a different location in the editor pane. + * @param line A new line + * @param location Coordinates of the new location + */ + public void move(int line, Point location) { + int oldLine = this.line; + this.line = line; + if (oldLine != line) { + pchs.firePropertyChange(PROP_LINE, oldLine, line); + } + Point oldLocation = this.location; + this.location = location; + if (!oldLocation.equals(location)) { + pchs.firePropertyChange(PROP_LOCATION, oldLocation, location); + } + } + + /** + * Set a textual comment to this pin. + * @param comment The user comment. + */ + public void setComment(String comment) { + String oldComment = this.comment; + this.comment = comment; + if (!Objects.equals(oldComment, comment)) { + pchs.firePropertyChange(PROP_COMMENT, oldComment, comment); + } + } + + /** + * Get the comment of this pin. + * @return The comment or null when no comment is set. + */ + public String getComment() { + return comment; + } + + void setVpId(String vpId) { + this.vpId = vpId; + } + + String getVpId() { + return vpId; + } + + /** + * Add a listener for property change events to this editor pin. + * @param listener The listener + */ + public void addPropertyChangeListener(PropertyChangeListener listener) { + pchs.addPropertyChangeListener(listener); + } + + /** + * Remove a listener for property change events from this editor pin. + * @param listener The listener + */ + public void removePropertyChangeListener(PropertyChangeListener listener) { + pchs.removePropertyChangeListener(listener); + } +} diff --git a/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/PinWatchUISupport.java b/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/PinWatchUISupport.java new file mode 100644 --- /dev/null +++ b/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/PinWatchUISupport.java @@ -0,0 +1,316 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2016 Sun Microsystems, Inc. + */ +package org.netbeans.spi.debugger.ui; + +import java.beans.Customizer; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.netbeans.api.debugger.DebuggerEngine; +import org.netbeans.api.debugger.DebuggerManager; +import org.netbeans.api.debugger.DebuggerManagerAdapter; +import org.netbeans.api.debugger.Watch; +import org.netbeans.api.debugger.Watch.Pin; +import org.netbeans.modules.debugger.ui.annotations.WatchAnnotationProvider; +import org.netbeans.spi.debugger.DebuggerServiceRegistration; +import org.openide.loaders.DataObjectNotFoundException; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; + +/** + * Access to the default UI implementation of pin watches in editor. + * Watches that are to be pinned by this support class, need to use the + * {@link EditorPin} as the {@link Watch#getPin()} and an implementation + * of {@link ValueProvider} needs to be register via {@link DebuggerServiceRegistration}, + * which acts as a supplier of the watch value. + *

+ * Use {@link #pin(org.netbeans.api.debugger.Watch, java.lang.String)} to pin + * the watch into editor where the corresponding {@link EditorPin} points at + * and provide valueProviderId of the corresponding registered + * {@link ValueProvider} which handles the value updates. + * + * @author Martin Entlicher + * @since 2.53 + */ +public final class PinWatchUISupport { + + private static final PinWatchUISupport INSTANCE = new PinWatchUISupport(); + private final Object valueProvidersLock = new Object(); + private Map valueProviders; + + private PinWatchUISupport() { + WatchAnnotationProvider.PIN_SUPPORT_ACCESS = new WatchAnnotationProvider.PinSupportedAccessor() { + @Override + public ValueProvider getValueProvider(EditorPin pin) { + String id = pin.getVpId(); + if (id == null) { + return null; + } + synchronized (valueProvidersLock) { + return getValueProviders().get(id); + } + } + }; + DebuggerManager.getDebuggerManager().addDebuggerListener( + DebuggerManager.PROP_CURRENT_ENGINE, + new DebuggerManagerAdapter() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + refreshValueProviders(); + } + } + ); + } + + /** + * Get the default instance of this class. + * @return An instance of {@link PinWatchUISupport} class. + */ + public static PinWatchUISupport getDefault() { + return INSTANCE; + } + + /** + * Pin the watch into an editor. + * The pinned watch need to return {@link EditorPin} from {@link Watch#getPin()} + * and a {@link ValueProvider} needs to be registered for the provided + * valueProviderId. + * @param watch A watch to pin at the location determined by the {@link EditorPin}. + * @param valueProviderId An id of a registered {@link ValueProvider}. + * @throws IllegalArgumentException is thrown when {@link Watch#getPin()} + * does not return an {@link EditorPin}, or + * when we're not able to find the editor + * where {@link EditorPin} points at. + */ + public void pin(Watch watch, String valueProviderId) throws IllegalArgumentException { + Pin wpin = watch.getPin(); + if (!(wpin instanceof EditorPin)) { + throw new IllegalArgumentException("Unsupported pin: "+wpin); + } + synchronized (valueProvidersLock) { + if (!getValueProviders().containsKey(valueProviderId)) { + valueProviders.put(valueProviderId, new DelegatingValueProvider(valueProviderId)); + } + } + EditorPin pin = (EditorPin) wpin; + pin.setVpId(valueProviderId); + try { + WatchAnnotationProvider.PIN_SUPPORT_ACCESS.pin(watch); + } catch (DataObjectNotFoundException ex) { + throw new IllegalArgumentException("Unable to find the pin's editor.", ex); + } + } + + private Map getValueProviders() { + synchronized (valueProvidersLock) { + if (valueProviders == null) { + valueProviders = new HashMap<>(); + refreshValueProviders(); + } + return valueProviders; + } + } + + private void refreshValueProviders() { + DebuggerManager dm = DebuggerManager.getDebuggerManager (); + DebuggerEngine e = dm.getCurrentEngine (); + List providers; + if (e == null) { + providers = dm.lookup (null, ValueProvider.class); + } else { + providers = DebuggerManager.join(e, dm).lookup (null, ValueProvider.class); + } + if (!providers.isEmpty()) { + synchronized (valueProvidersLock) { + if (valueProviders == null) { + valueProviders = new HashMap<>(); + } + Set existingProviderIds = new HashSet<>(); + for (ValueProvider provider : providers) { + String id = provider.getId(); + existingProviderIds.add(id); + DelegatingValueProvider dvp = valueProviders.get(id); + if (dvp == null) { + dvp = new DelegatingValueProvider(id); + valueProviders.put(id, dvp); + } + dvp.setDelegate(provider); + } + Set staleProviderIds = new HashSet<>(valueProviders.keySet()); + staleProviderIds.removeAll(existingProviderIds); + for (String staleId : staleProviderIds) { + valueProviders.get(staleId).setDelegate(null); + } + } + } + } + + private static final class DelegatingValueProvider implements ValueProvider { + + private final String id; + private volatile ValueProvider delegate; + private final Map listeners = new HashMap<>(); + + DelegatingValueProvider(String id) { + this.id = id; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getValue(Watch watch) { + ValueProvider vp = delegate; + if (vp != null) { + return vp.getValue(watch); + } else { + return null; + } + } + + @Override + public synchronized void setChangeListener(Watch watch, ValueChangeListener chl) { + ValueProvider vp = delegate; + if (vp != null) { + vp.setChangeListener(watch, chl); + } + listeners.put(watch, chl); + } + + @Override + public synchronized void unsetChangeListener(Watch watch) { + ValueProvider vp = delegate; + if (vp != null) { + vp.unsetChangeListener(watch); + } + listeners.remove(watch); + } + + synchronized void setDelegate(ValueProvider delegate) { + this.delegate = delegate; + if (delegate == null) { + for (Map.Entry wvl : listeners.entrySet()) { + wvl.getValue().valueChanged(wvl.getKey()); + } + } else { + for (Map.Entry wvl : listeners.entrySet()) { + delegate.setChangeListener(wvl.getKey(), wvl.getValue()); + } + } + } + + } + + /** + * Provider of pinned watch value. + * Register an implementation of this class via {@link DebuggerServiceRegistration} + * for the corresponding debugger session ID path. + */ + public static interface ValueProvider { + + /** + * Get a unique ID of this value provider. + * Use this ID when pinning a watch via {@link #pin(org.netbeans.api.debugger.Watch, java.lang.String)}. + * @return An ID of this value provider. + */ + String getId(); + + /** + * Get current value of pinned watch. This method must not block, + * it's called synchronously in the EQ thread. This method should return + * most recent value of the watch, or the same instance which + * {@link #getEvaluatingText()} returns when the watch value is being computed, + * or null when the watch can not be resolved. + * @param watch the watch whose value is to be returned. + * @return The current value of the watch, or {@link #getEvaluatingText()}, + * or null. + */ + String getValue(Watch watch); + + /** + * Get a localized text to be displayed when the watch is being evaluated. + * The pin watch UI highlights changed values. It uses this string to + * distinguish real watch values. Return the same instance from + * {@link #getValue(org.netbeans.api.debugger.Watch)} when the watch is + * being computed. + * @return A localized text displayed while the watch is evaluating. + */ + @NbBundle.Messages("WATCH_EVALUATING=Evaluating...") + default String getEvaluatingText() { + return Bundle.WATCH_EVALUATING(); + } + + /** + * Allows to set a value change listener for a specific watch. + * @param watch The watch to listen for changes + * @param chl The value change listener. + */ + void setChangeListener(Watch watch, ValueChangeListener chl); + + /** + * Unset a value change listener for a specific watch. + * Use this method to unregister the listeners and free up associated resources. + * @param watch The watch to unset the listener from. + */ + void unsetChangeListener(Watch watch); + + /** + * Listener for watch value changes. + */ + public static interface ValueChangeListener { + + /** + * Notify that a watch value has changed. + * {@link ValueProvider#getValue(org.netbeans.api.debugger.Watch)} + * then returns the new value. + * @param watch The watch whose value has changed. + */ + void valueChanged(Watch watch); + } + } +}