开发一个JSP编辑器 Eclipse 提供了 TextEditor,作为文本编辑器的父类。由于 Editor 的开发不是本文的重点,不做具体论述。我们可以利用 Eclipse 的 Plugin 项目向导,生成一个简单的 JSP 编辑器:
(1)点击 File 菜单,New -> Project -> Plug-in Project ;
(2)输入项目名称 JSP_DEBUG,下一步;
(3)输入 plugin ID : com.jsp.debug
Plugin Class name : com.jsp.debug.JSP_DebugPlugin
(4)选择用模板创建
使用 Plug-in with editor,输入
Java Package Name :com.jsp.editors
Editor Class Name :JSPEditor
File extension :jsp
一个 jsp editor 就产生了。
运行这个Plugin,新建一个JAVA项目,新建一个 Hello.jsp 和 greeting.jsp,在 Navigator 视图双击 jsp,这个editor就打开了。
在JSP编辑器中设置断点 在编辑器中添加断点的操作方式有两种,一种是在编辑器左侧垂直标尺上双击,另一种是在左侧垂直标尺上点击鼠标右键,选择菜单"添加/删除断点"。
在 Eclipse 的实现中,添加断点实际上就是为 IFile 添加一个marker ,类型是IBreakpoint.BREAKPOINT_MARKER,然后将断点注册到 BreakpointManager。
BreakpointManager 将产生一个 BreakpointRequest,通知正在运行的JVM Target,如果此时还没有启动 JVM,会在 JVM 启动的时候,将所有断点一起通知 JVM Target。
添加断点使用一个 AbstractRulerActionDelegate,重载 createAction 方法,返回一个 IAction ManageBreakpointRulerAction动作:
public class ManageBreakpointRulerActionDelegate extends AbstractRulerActionDelegate{
protected IAction createAction(ITextEditor editor, IVerticalRulerInfo rulerInfo) {
return new ManageBreakpointRulerAction(rulerInfo, editor);
}
}
|
为了将 ManageBreakpointRulerActionDelegate 添加到文本编辑器左侧标尺的鼠标右键菜单,并且能够处理左侧标尺的鼠标双击事件,在 plugin.xml 中加入定义。
处理双击事件:
<extension point="org.eclipse.ui.editorActions">
<editorContribution
targetID="com.jiaoly.editors.JSPEditor"
id="com.jiaoly.debug.ManageBreakpointRulerActionDelegate">
<action
label="添加/删除断点"
class="com.jiaoly.debug.ManageBreakpointRulerActionDelegate"
actionID="RulerDoubleClick"
id="com.jiaoly.debug.ManageBreakpointRulerActionDelegate">
</action>
</editorContribution>
</extension>
|
添加右键菜单:
<extension point="org.eclipse.ui.popupMenus">
<viewerContribution
targetID="#TextRulerContext"
id="com.jiaoly.debug.ManageBreakpointRulerActionDelegate">
<action
label="添加/删除断点"
class="com.jiaoly.debug.ManageBreakpointRulerActionDelegate"
menubarPath="addition"
id="com.jiaoly.debug.ManageBreakpointRulerActionDelegate">
</action>
</viewerContribution>
</extension>
|
ManageBreakpointRulerAction 是实际添加断点的Action,实现了 IUpdate 接口,这个Action的工作,就是判断当前选中行是否存在断点类型的 Marker,如果不存在创建一个,如果存在,将它删除。
public class ManageBreakpointRulerAction extends Action implements IUpdate{
private IVerticalRulerInfo rulerInfo;
private ITextEditor textEditor;
private String BPmarkerType ; //当点Marker的类型
private List allMarkers; //当前鼠标点击行所有的Marker
private String addBP; //Action 的显示名称
public ManageBreakpointRulerAction(IVerticalRulerInfo ruler, ITextEditor editor){
this.rulerInfo = ruler;
this.textEditor = editor;
BPmarkerType = IBreakpoint.BREAKPOINT_MARKER;
addBP = "添加/删除断点"; //$NON-NLS-1$
setText(this.addBP);
}
public void update() {
this.allMarkers = this.fetchBPMarkerList();
}
public void run(){
if(this.allMarkers.isEmpty())
this.addMarker();
else
this.removeMarkers(this.allMarkers);
}
}
|
update 方法会在点击时首先调用,这时就可以收集当前选中行是否有marker了(调用fetchBPMarkerList方法),如果有,就保存在 变量allMarkers 中。由于ManageBreakpointRulerAction每一次都产生一个新的实例,因此不会产生冲突。
下面是update的调用栈,可以看出,update方法是在鼠标点击事件中被调用的:
ManageBreakpointRulerAction.update() line: 55
ManageBreakpointRulerActionDelegate(AbstractRulerActionDelegate).update() line: 114
ManageBreakpointRulerActionDelegate(AbstractRulerActionDelegate).mouseDown(MouseEvent) line: 139
|
updae被调用后,会执行 run 方法,就可以根据 allMarkers.isEmpty() 确定要删除还是添加 marker 了。
添加断点的时候,首先利用 IVerticalRulerInfo,获取鼠标点击的行号,根据行号,从 Document 模型中取得该行的描述IRegion,得到开始字符位置和结束字符位置,创建一个 JSP 断点。
protected void addMarker() {
IEditorInput editorInput= this.getTextEditor().getEditorInput();
IDocument document= this.getDocument();
//the line number of the last mouse button activity
int rulerLine= this.getRulerInfo().getLineOfLastMouseButtonActivity();
try{
int lineNum = rulerLine + 1;
if(lineNum > 0){
//Returns a description of the specified line
IRegion iregion = document.getLineInformation(lineNum - 1);
int charStart = iregion.getOffset();
int charEnd = (charStart + iregion.getLength()) - 1;
JSPDebugUtility.createJspLineBreakpoint(this.getResource(),
lineNum, charStart, charEnd);
}
}catch(CoreException coreexception){
coreexception.printStackTrace();
}
catch(BadLocationException badlocationexception){
badlocationexception.printStackTrace();
}
}
|
注册 JSP 断点为支持 JSR-45 规范,Eclipse 中提供了 JavaStratumLineBreakpoint。不过它目前是一个 internal 的实现,在以后的版本中不能保证不作修改。这里为了简单起见,直接从 JavaStratumLineBreakpoint 继承。
public class JSPBreakpoint extends JavaStratumLineBreakpoint {
public JSPBreakpoint(IResource resource, String stratum, String sourceName,
String sourcePath, String classNamePattern, int lineNumber,
int charStart, int charEnd, int hitCount, boolean register,
Map attributes) throws DebugException {
super(resource, stratum, sourceName, sourcePath, classNamePattern,
lineNumber, charStart, charEnd, hitCount, register, attributes);
}
}
|
查看 JavaStratumLineBreakpoint 的源代码可以知道,创建 JavaStratumLineBreakpoint 的时候做了两件事情:
(1) 创建断点类型的 marker, 并且设置了marker的属性resource.createMarker(markerType);
(2) 将断点注册到断点管理器
DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(this); 断点管理器负责产生一个 BreakpointRequest,通知正在运行的JVM Target 如果此时还没有启动 JVM,会在 JVM 启动的时候,将所有断点一起通知 JVM Target。
下面是 JavaStratumLineBreakpoint 构造函数中的代码:
IWorkspaceRunnable wr= new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
// create the marker
setMarker(resource.createMarker(markerType));
// modify pattern
String pattern = classNamePattern;
if (pattern != null && pattern.length() == 0) {
pattern = null;
}
// add attributes
addLineBreakpointAttributes(attributes, getModelIdentifier(), true,
lineNumber, charStart, charEnd);
addStratumPatternAndHitCount(attributes, stratum, sourceName,
sourcePath, pattern, hitCount);
// set attributes
ensureMarker().setAttributes(attributes);
register(register);
}
};
run(null, wr);
protected void register(boolean register) throws CoreException {
if (register) {
DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(this);
} else {
setRegistered(false);
}
}
|
移除断点的时候,根据 marker 找到相应的 IBreakpoint,从 BreakpointManager 中移除 BreakpointManager 会自动删除 marker,通知 JVM Target。
breakpointManager = DebugPlugin.getDefault().getBreakpointManager();
IBreakpoint breakpoint = breakpointManager.getBreakpoint(IMarker);
breakpointManager.removeBreakpoint(breakpoint, true);
|
JSPBreakpoint 重载了父类的addToTarget(JDIDebugTarget target) 方法。重载这个方法的目的是根据不同的应用服务器,设置不同的 referenceTypeName和sourcePath。我们知道,每种应用服务器编译 JSP 产生Java Class 名称的规则都不相同,例如Tomcat编译Hello.jsp 产生的Java 类名为 org.apache.jsp. Hello_jsp,而WebSphere6.0 却是 com.ibm._jsp._Hello。只有确定服务器类型,才能知道referenceTypeName 和souecePath应该是什么。目前通过启动 JVM 时target 名称来判断应用服务器类型: String targetString = target.getLaunch().getLaunchConfiguration().getName(); 如果targetString 包含 Tomcat ,就认为是 Tomcat。
产生 referenceTypeName 后首先创建一个 ClassPrepareRequest 通知,然后从vm中取出所有的classes,如果是当前的 Class,再创建一个添加断点通知。之所以这样做,是因为有可能这个 Class 还没有被 JVM 加载,直接通知 JVM 没有任何意义。在 Class 被加载的时候,JVM 会通知 Eclipse,这个时候,才产生添加断点通知。需要指出的是,本文示例代码获取 referenceTypeName 的方法不是很完善:
(1) 仅仅实现了Tomcat 读者有兴趣可以实现更多的Web容器,例如 JBoss3 以上,WebSphere6.0
(2) 一些特殊情况没有处理例如 路径名为package的jsp,路径名或文件名带有数字的jsp
public void addToTarget(JDIDebugTarget target) throws CoreException {
IMarker marker = this.getMarker();
IResource resource = marker.getResource();
String targetString = target.getLaunch().getLaunchConfiguration().getName();
IJSPNameUtil util = JSPDebugUtility.getJSPNameUtil(targetString);
// pre-notification
fireAdding(target);
String referenceTypeName;
try {
referenceTypeName = getPattern();
//如果没有设置 Pattern, 根据 Server 的类型, 产生新的 Pattern
if(referenceTypeName == null ||
"".equals(referenceTypeName.trim()) ||
"*".equals(referenceTypeName.trim())){
referenceTypeName = util.referenceTypeName(resource);
}
} catch (CoreException e) {
JDIDebugPlugin.log(e);
return;
}
this.ensureMarker().setAttribute(TYPE_NAME, referenceTypeName);
String sourcePath = util.sourcePath(resource);
this.ensureMarker().setAttribute(JSPBreakpoint.SOURCE_PATH, sourcePath);
String classPrepareTypeName= referenceTypeName;
//如果这时 class 还没有被加载, 注册一个 ClassPrepareRequest 请求
//
//当 class 加载的时候, 首先会触发 JavaBreakpoint 的 handleClassPrepareEvent 方法
//调用 createRequest(target, event.referenceType()) --> newRequest() -->
// createLineBreakpointRequest() 创建 enable或disable 断点的请求
//
// 设置 enable/disable 动作在 configureRequest() --> updateEnabledState(request) 方法中
// 根据 getMarker().getAttribute(ENABLED, false) 确定断点是否有效
registerRequest(target.createClassPrepareRequest(classPrepareTypeName), target);
// create breakpoint requests for each class currently loaded
VirtualMachine vm = target.getVM();
if (vm == null) {
target.requestFailed("Unable_to_add_breakpoint_-_VM_disconnected._1"),
null); }
List classes = null;
try {
classes= vm.allClasses();
} catch (RuntimeException e) {
target.targetRequestFailed("JavaPatternBreakpoint.0"), e);
}
if (classes != null) {
Iterator iter = classes.iterator();
while (iter.hasNext()) {
ReferenceType type= (ReferenceType)iter.next();
if (installableReferenceType(type, target)) {
createRequest(target, type);
}
}
}
}
|