blob: b9f831e9ec7846222bd67252da701180e5b0e2e3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.spi.debugger.jpda;
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.Objects;
import org.netbeans.api.debugger.jpda.CallStackFrame;
import org.netbeans.api.debugger.jpda.JPDAStep;
import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.api.debugger.jpda.SmartSteppingFilter;
import org.netbeans.modules.debugger.jpda.apiregistry.DebuggerProcessor;
import org.netbeans.spi.debugger.ContextAwareService;
import org.netbeans.spi.debugger.ContextAwareSupport;
import org.netbeans.spi.debugger.ContextProvider;
/**
* Listens on stepping engine and defines classes / places the debugger can
* stop in.
*
* @author Jan Jancura
*/
public abstract class SmartSteppingCallback {
/**
* Defines default set of smart stepping filters. Method is called when
* a new JPDA debugger session is created.
*
* @param f a filter to be initialized
*/
public abstract void initFilter (SmartSteppingFilter f);
/**
* This method is called during stepping through debugged application.
* The execution is stopped when all registered <code>SmartSteppingCallback</code>s
* returns true.
*
* @param thread contains all available information about current position
* in debugged application
* @param f a filter
* @return true if execution should be stopped on the current position
*/
public abstract boolean stopHere (ContextProvider lookupProvider, JPDAThread thread, SmartSteppingFilter f);
/**
* This is an enhanced version of former {@link #stopHere(org.netbeans.spi.debugger.ContextProvider, org.netbeans.api.debugger.jpda.JPDAThread, org.netbeans.api.debugger.jpda.SmartSteppingFilter)}
* method, which is called during stepping through debugged application.
* The <code>frame</code> argument allows this method to be called for specific stack frames,
* to check if the execution could be suspended there. This is valuable e.g. for step out.
* When a top frame is provided, this method can suggest a specific step to continue with,
* in case it's not possible to stop at the given location.
*
* The default implementation calls {@link #stopHere(org.netbeans.spi.debugger.ContextProvider, org.netbeans.api.debugger.jpda.JPDAThread, org.netbeans.api.debugger.jpda.SmartSteppingFilter)}
* when called with a top frame and throws an {@link UnsupportedOperationException} otherwise.
*
* @param lookupProvider The debugger services lookup
* @param frame The frame in question
* @param f a filter
* @return whether the debugger can stop at this location, or whether it should continue with a step.
* @since 3.5
*/
public StopOrStep stopAt(ContextProvider lookupProvider, CallStackFrame frame, SmartSteppingFilter f) {
int depth = frame.getFrameDepth();
if (depth > 0) {
throw new UnsupportedOperationException("Not supporting frames with depth > 0");
}
boolean stop = stopHere(lookupProvider, frame.getThread(), f);
return stop ? StopOrStep.stop() : StopOrStep.skip();
}
/**
* Information about a possibility to stop at the given location,
* or suggestion to perform a step.
* Used by {@link #stopAt(org.netbeans.spi.debugger.ContextProvider, org.netbeans.api.debugger.jpda.CallStackFrame, org.netbeans.api.debugger.jpda.SmartSteppingFilter)}
* @since 3.5
*/
public static final class StopOrStep {
private static final StopOrStep STOP = new StopOrStep(true, 0, 0);
private static final StopOrStep SKIP = new StopOrStep(false, 0, 0);
private final boolean stop;
private final int stepSize;
private final int stepDepth;
/**
* Express the possibility to stop.
* @return information about a possibility to stop at the given location.
*/
public static StopOrStep stop() {
return STOP;
}
/**
* Express the necessity to skip the given location,
* using whatever the default debugger action is at the moment.
* @return information about the necessity to skip the given location.
*/
public static StopOrStep skip() {
return SKIP;
}
/**
* Express the necessity to perform a step at the given location.
* @param stepSize the step size,
* one of {@link #JPDAStep.STEP_LINE} or {@link #JPDAStep.STEP_MIN},
* or <code>0</code> for the default size.
* @param stepDepth the step depth,
* one of {@link #JPDAStep.STEP_INTO}, {@link #JPDAStep.STEP_OVER},
* {@link #JPDAStep.STEP_OUT}, or <code>0</code> for the default depth.
* @return the step information instance.
* throws {@link IllegalArgumentException} when the size or depth is wrong.
*/
public static StopOrStep step(int stepSize, int stepDepth) {
return new StopOrStep(false, stepSize, stepDepth);
}
private StopOrStep(boolean stop, int stepSize, int stepDepth) {
this.stop = stop;
switch (stepSize) {
case 0:
case JPDAStep.STEP_MIN:
case JPDAStep.STEP_LINE:
// O.K.
break;
default:
throw new IllegalArgumentException("Wrong step size: "+stepSize);
}
switch (stepDepth) {
case 0:
case JPDAStep.STEP_INTO:
case JPDAStep.STEP_OVER:
case JPDAStep.STEP_OUT:
// O.K.
break;
default:
throw new IllegalArgumentException("Wrong step depth: "+stepDepth);
}
this.stepSize = stepSize;
this.stepDepth = stepDepth;
}
/**
* Whether this is a possibility to stop.
* If yes, values of step size and step depth are irrelevant.
* @return <code>true</code> if it's possible to stop, <code>false</code> otherwise.
*/
public boolean isStop() {
return stop;
}
/**
* Get the step size.
* @return One of {@link #JPDAStep.STEP_LINE} or {@link #JPDAStep.STEP_MIN},
* or <code>0</code> for the default size.
*/
public int getStepSize() {
return stepSize;
}
/**
* Get the step depth.
* @return One of {@link #JPDAStep.STEP_INTO}, {@link #JPDAStep.STEP_OVER},
* {@link #JPDAStep.STEP_OUT}, or <code>0</code> for the default depth.
*/
public int getStepDepth() {
return stepDepth;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof StopOrStep)) {
return false;
}
StopOrStep ss = (StopOrStep) obj;
return stop == ss.stop &&
stepSize == ss.stepSize &&
stepDepth == ss.stepDepth;
}
@Override
public int hashCode() {
return Objects.hash(stop, stepSize, stepDepth);
}
@Override
public String toString() {
return "StopOrStep["+stop+","+stepSize+","+stepDepth+"]";
}
}
/**
* Declarative registration of a SmartSteppingCallback 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.
*
* @author Martin Entlicher
* @since 2.19
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface Registration {
/**
* An optional path to register this implementation in.
*/
String path() default "";
}
static class ContextAware extends SmartSteppingCallback implements ContextAwareService<SmartSteppingCallback> {
private String serviceName;
private ContextAware(String serviceName) {
this.serviceName = serviceName;
}
public SmartSteppingCallback forContext(ContextProvider context) {
return (SmartSteppingCallback) ContextAwareSupport.createInstance(serviceName, context);
}
@Override
public void initFilter(SmartSteppingFilter f) {
assert false;
}
@Override
public boolean stopHere(ContextProvider lookupProvider, JPDAThread thread, SmartSteppingFilter f) {
assert false;
throw new UnsupportedOperationException("Not supported.");
}
/**
* Creates instance of <code>ContextAwareService</code> based on layer.xml
* attribute values
*
* @param attrs attributes loaded from layer.xml
* @return new <code>ContextAwareService</code> instance
*/
static ContextAwareService createService(Map attrs) throws ClassNotFoundException {
String serviceName = (String) attrs.get(DebuggerProcessor.SERVICE_NAME);
return new ContextAware(serviceName);
}
}
}