diff --git a/api.debugger/src/org/netbeans/api/debugger/EngineBreakpoints.java b/api.debugger/src/org/netbeans/api/debugger/EngineBreakpoints.java new file mode 100644 --- /dev/null +++ b/api.debugger/src/org/netbeans/api/debugger/EngineBreakpoints.java @@ -0,0 +1,178 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 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 2015 Sun Microsystems, Inc. + */ + +package org.netbeans.api.debugger; + +import java.beans.PropertyChangeListener; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import org.netbeans.spi.debugger.EngineBreakpointsProvider; + +/** + * This class handles the debugger engine-related breakpoints and provides + * a possibility of breakpoints activation/deactivation, which is independent + * on their enabled/disabled state. + * + * @author Martin Entlicher + * @since 1.51 + */ +public final class EngineBreakpoints { + + /** + * Property change fired when breakpoints activation/deactivation changes. + */ + public static final String PROP_BREAKPOINTS_ACTIVE = "breakpointsActive"; // NOI18N + /** + * Property change fired when a set of engine-related breakpoints is changed. + */ + public static final String PROP_BREAKPOINTS_CHANGED = "breakpointsChanged"; // NOI18N + + // These two lists are used as a trivial weak map from DebuggerEngine to EngineBreakpoints, + // where both keys and values are held weakly. + private static final List> dEngines = new LinkedList<>(); + private static final List> eBreakpoints = new LinkedList<>(); + + private final EngineBreakpointsProvider ebp; + + /** + * Get an EngineBreakpoints instance for a debugger engine. + * @param debuggerEngine the debugger engine. + * @return The EngineBreakpoints instance, or null when the engine + * does not provide {@link EngineBreakpointsProvider} in it's lookup. + */ + public static EngineBreakpoints get(DebuggerEngine debuggerEngine) { + EngineBreakpointsProvider ebp = debuggerEngine.lookupFirst(null, EngineBreakpointsProvider.class); + if (ebp == null) { + return null; + } + synchronized (dEngines) { + for (int i = 0; i < dEngines.size(); i++) { + DebuggerEngine de = dEngines.get(i).get(); + if (de == null) { + dEngines.remove(i); + eBreakpoints.remove(i); + i--; + continue; + } + if (de == debuggerEngine) { + EngineBreakpoints eb = eBreakpoints.get(i).get(); + if (eb != null) { + return eb; + } + } + } + // Not cached, create a new one: + EngineBreakpoints eb = new EngineBreakpoints(ebp); + dEngines.add(new WeakReference<>(debuggerEngine)); + eBreakpoints.add(new WeakReference<>(eb)); + return eb; + } + } + + private EngineBreakpoints(EngineBreakpointsProvider ebp) { + this.ebp = ebp; + } + + /** + * Test if breakpoint deactivation is supported. + * When false is returned, {@link #setBreakpointsActive(boolean)} + * throws UnsupportedOperationException when called with false argument. + * @return true when engine-related breakpoints can be deactivated, + * false otherwise. + */ + public boolean canDeactivateBreakpoints() { + return ebp.canDeactivateBreakpoints(); + } + + /** + * Test if the engine's breakpoints are currently active. + * @return true when breakpoints are active, + * false otherwise. + */ + public boolean areBreakpointsActive() { + return ebp.areBreakpointsActive(); + } + + /** + * Activate or deactivate breakpoints handled by this debugger engine. + * The breakpoints activation/deactivation is independent on breakpoints enabled/disabled state. + * + * @param active true to activate breakpoints, + * false to deactivate them. + * @throws UnsupportedOperationException when there is an attempt to deactivate + * breakpoints even though {@link #canDeactivateBreakpoints()} return false + */ + public void setBreakpointsActive(boolean active) throws UnsupportedOperationException { + ebp.setBreakpointsActive(active); + } + + /** + * Get the set of breakpoints that are managed by this engine. + * These are the breakpoints that are subject of activation/deactivation + * if it's supported. + * @return A set of breakpoints managed by this engine. + */ + public Set getEngineBreakpoints() { + return ebp.getEngineBreakpoints(); + } + + /** + * Add a property change listener to be notified about properties + * defined as PROP_* constants. + * @param l a property change listener + */ + public final void addPropertyChangeListener(PropertyChangeListener l) { + ebp.addPropertyChangeListener(l); + } + + /** + * Remove a property change listener. + * @param l a property change listener + */ + public final void removePropertyChangeListener(PropertyChangeListener l) { + ebp.removePropertyChangeListener(l); + } + +} diff --git a/api.debugger/src/org/netbeans/spi/debugger/EngineBreakpointsProvider.java b/api.debugger/src/org/netbeans/spi/debugger/EngineBreakpointsProvider.java new file mode 100644 --- /dev/null +++ b/api.debugger/src/org/netbeans/spi/debugger/EngineBreakpointsProvider.java @@ -0,0 +1,280 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 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 2015 Sun Microsystems, Inc. + */ + +package org.netbeans.spi.debugger; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import org.netbeans.api.debugger.Breakpoint; +import org.netbeans.api.debugger.DebuggerEngine; +import org.netbeans.api.debugger.EngineBreakpoints; +import static org.netbeans.api.debugger.EngineBreakpoints.PROP_BREAKPOINTS_ACTIVE; +import static org.netbeans.api.debugger.EngineBreakpoints.PROP_BREAKPOINTS_CHANGED; +import org.netbeans.debugger.registry.ContextAwareServiceHandler; + +/** + * Provider of debugger engine-related breakpoints and a possibility of their + * activation/deactivation, which is independent on the enabled/disabled state. + * Register an implementation of this class for an appropriate debugger engine + * via {@link Registration} annotation. + * + * @author Martin Entlicher + * @since 1.51 + */ +public abstract class EngineBreakpointsProvider { + + final PropertyChangeSupport pchs = new PropertyChangeSupport(this); + private final AtomicBoolean breakpointsActive = new AtomicBoolean(areBreakpointsActiveInitially()); + + /** + * Provides the initial state of breakpoints active property. + * Override to provide the initial state different from the default one. + * @return true by default. + */ + protected boolean areBreakpointsActiveInitially() { + return true; + } + + /** + * Test if breakpoint deactivation is supported. + * When false is returned, {@link #setBreakpointsActive(boolean)} + * throws UnsupportedOperationException when called with false argument. + * @return true when engine-related breakpoints can be deactivated, + * false otherwise. + */ + public abstract boolean canDeactivateBreakpoints(); + + /** + * Activate or deactivate breakpoints handled by this debugger engine. + * The breakpoints activation/deactivation is independent on breakpoints enabled/disabled state. + * + * @param active true to activate breakpoints, + * false to deactivate them. + * @throws UnsupportedOperationException when there is an attempt to deactivate + * breakpoints even though {@link #canDeactivateBreakpoints()} return false + */ + public final void setBreakpointsActive(boolean active) throws UnsupportedOperationException { + if (!active && !canDeactivateBreakpoints()) { + throw new UnsupportedOperationException("Can not deactivate breakpoints"); // NOI18N + } + if (breakpointsActive.getAndSet(active) != active) { + notifyBreakpointsActive(active); + fireBreakpointsActive(active); + } + } + + /** + * Test if the engine's breakpoints are currently active. + * @return true when breakpoints are active, + * false otherwise. + */ + public final boolean areBreakpointsActive() { + return breakpointsActive.get(); + } + + /** + * The implementation class is notified about breakpoints activation/deactivation + * requests via this method. The implementation of this method should + * actually activate/deactivate breakpoints in the corresponding debugger engine. + * @param active true to activate breakpoints, + * false otherwise. + */ + protected abstract void notifyBreakpointsActive(boolean active); + + private void fireBreakpointsActive(boolean active) { + pchs.firePropertyChange(PROP_BREAKPOINTS_ACTIVE, !active, active); + } + + /** + * Get the set of breakpoints that are managed by this engine. + * These are the breakpoints that are subject of activation/deactivation + * if it's supported. + * @return A set of breakpoints managed by this engine. + */ + public abstract Set getEngineBreakpoints(); + + /** + * Notify about changes of breakpoints that are managed by this engine. + * @param removed a removed breakpoint, or null + * @param added an added breakpoint, or null + */ + protected final void fireBreakpointsChanged(Breakpoint removed, Breakpoint added) { + pchs.firePropertyChange(PROP_BREAKPOINTS_CHANGED, removed, added); + } + + /** + * Add a property change listener to be notified about properties + * defined in {@link EngineBreakpoints}.PROP_* + * @param l a property change listener + */ + public final void addPropertyChangeListener(PropertyChangeListener l) { + pchs.addPropertyChangeListener(l); + } + + /** + * Remove a property change listener. + * @param l a property change listener + */ + public final void removePropertyChangeListener(PropertyChangeListener l) { + pchs.removePropertyChangeListener(l); + } + + /** + * Declarative registration of an EngineBreakpointsProvider implementation. + * By marking the implementation class with this annotation, + * you automatically register that implementation for use by debugger. + * The class must be public and have a public constructor which takes + * no arguments or takes {@link ContextProvider} as an argument. + */ + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE}) + public @interface Registration { + + /** + * The path to register this implementation in. + * Usually the session/engine ID. + */ + String path(); + + /** + * Test if breakpoint deactivation is supported. + * By default, the provider instance is created immediately. + * This method can be used to delay the instantiation of the + * implementation class for performance reasons. + * @return true when engine-related breakpoints can be deactivated, + * false otherwise. + */ + boolean canDeactivateBreakpoints() default false; + } + + static class ContextAware extends EngineBreakpointsProvider implements ContextAwareService { + + private final String serviceName; + private ContextProvider context; + private EngineBreakpointsProvider delegate; + + private boolean canDeactivateBreakpoints; + + private ContextAware(String serviceName, boolean canDeactivateBreakpoints) { + this.serviceName = serviceName; + this.canDeactivateBreakpoints = canDeactivateBreakpoints; + } + + private ContextAware(String serviceName, boolean canDeactivateBreakpoints, + ContextProvider context) { + this.serviceName = serviceName; + this.canDeactivateBreakpoints = canDeactivateBreakpoints; + this.context = context; + } + + private synchronized EngineBreakpointsProvider getDelegate() { + if (delegate == null) { + delegate = (EngineBreakpointsProvider) ContextAwareSupport.createInstance(serviceName, context); + if (delegate == null) { + throw new IllegalStateException("No instance created for service "+serviceName+", context = "+context); + } + for (PropertyChangeListener l : pchs.getPropertyChangeListeners()) { + delegate.addPropertyChangeListener(l); + pchs.removePropertyChangeListener(l); + } + } + return delegate; + } + + @Override + public boolean canDeactivateBreakpoints() { + EngineBreakpointsProvider ebpDelegate; + synchronized (this) { + ebpDelegate = this.delegate; + } + if (ebpDelegate != null) { + return ebpDelegate.canDeactivateBreakpoints(); + } else { + return canDeactivateBreakpoints; + } + } + + @Override + protected void notifyBreakpointsActive(boolean active) { + getDelegate().notifyBreakpointsActive(active); + } + + @Override + public Set getEngineBreakpoints() { + return getDelegate().getEngineBreakpoints(); + } + + @Override + public EngineBreakpointsProvider forContext(ContextProvider context) { + if (context == this.context) { + return this; + } else { + return getDelegate(); + } + } + + /** + * Creates instance of ContextAwareService based on layer.xml + * attribute values + * + * @param attrs attributes loaded from layer.xml + * @return new ContextAwareService instance + */ + static ContextAwareService createService(Map attrs) throws ClassNotFoundException { + String serviceName = (String) attrs.get(ContextAwareServiceHandler.SERVICE_NAME); + String cdbStr = (String) attrs.get(ContextAwareServiceHandler.SERVICE_CAN_DEACTIVATE_BREAKPOINTS); + boolean cdb = Boolean.parseBoolean(cdbStr); + ContextAware ca = new EngineBreakpointsProvider.ContextAware(serviceName, + cdb); + return ca; + } + + } + +}