開發背景
在項目開發的過程中,出於JSP性能方面考慮,在條件允許的情況下我們常常會采用靜態include的形式來替代動態include。
[html]
<@include file="reuse.html">
=>
<jsp:include page="reuse.html" />
<@include file="reuse.html">
=>
<jsp:include page="reuse.html" />但靜態包含為開發帶來的副作用就是,每次修改了被包含頁面時,必須同時刷新屬主文件,否則修改過的內容不會被體現出來,所以開發者常常會在屬主文件中隨意添加或刪除幾個空白字符並保存,為了就是能夠讓文件的最後修改時間得到更新,迫使服務器重新編譯整個JSP,從而使得被包含的JSP內容可以正確地展現出來。
但是每次手工刷新比較耗時,而且在涉及到多個文件的時候操作起來也比較繁瑣,所以就萌生了一個利用eclipse插件來進行項目文件刷新的想法。
項目一覽
由於這個插件功能很少,所以我們只需要准備好最基本條件即可,首先創建一個插件工程,然後就會看到一個如下的文件目錄結構。
[plain]
C:\WORKSTATION\WORKSPACE\INFO.WOODY.TOOL.TOUCHIT
| .classpath
| .project
| build.properties
| plugin.xml
|
+---icons
| touchjsp.jpeg
|
+---META-INF
| MANIFEST.MF
|
\---src
\---info
\---woody
\---tool
\---touchit
| Activator.java
|
\---handlers
TouchHandler.java
C:\WORKSTATION\WORKSPACE\INFO.WOODY.TOOL.TOUCHIT
| .classpath
| .project
| build.properties
| plugin.xml
|
+---icons
| touchjsp.jpeg
|
+---META-INF
| MANIFEST.MF
|
\---src
\---info
\---woody
\---tool
\---touchit
| Activator.java
|
\---handlers
TouchHandler.javaicons目錄中的touchjsp.jpeg文件就是我們的插件圖標,總得有個臉面不是:)
Activator.java是自動生成的,不必理會,它就是一個插件裝載的入口程序。
TouchHandler.java是核心內容,就是幫助我們刷新文件用的。
其余的五個就是配置文件,.classpath和.project是每個Java工程必不可少的元信息。build.properties/plugin.xml/MANIFEST.MF是與插件相關的配置文件。
項目配置
.classpath
.project
插件配置
插件的配置文件(build.properties/plugin.xml/MANIFEST.MF)都可以在插件編輯器中直接編輯,無需操作原始文件。當然,如果你對手工操作比較有自信的話也可以直接操作原始文件的。下面以MANIFEST.MF為例來說明如何在插件編輯器中對配置文件進行編輯。
插件編輯器的下方把配置文件分配到幾個不同的選項卡中,MANIFEST.MF對應前兩個選項卡,分別是Overview和Dependencies。我們先來看下MANIFEST.MF的原始文件內容:
[plain]
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Touchit
Bundle-SymbolicName: info.woody.tool.touchit;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: info.woody.tool.touchit.Activator
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.ui.ide
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Eclipse-AutoStart: true
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Touchit
Bundle-SymbolicName: info.woody.tool.touchit;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: info.woody.tool.touchit.Activator
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.ui.ide
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Eclipse-AutoStart: true接下來是選項卡Overview和Dependencise的截圖。從截圖中我們可以看出,插件的基本信息都在Overview選項卡中,而依賴包都分配在Dependencies選項卡中。除了MANIFEST.MF外,另外兩個配置文件也都有各自對應的選項卡,具體信息可以從下面的對應關系中找到。其實不論是在編輯器中編輯,還是直接修改原始文件的內容,這兩種操作的結果都是一樣的。
插件編輯器選項卡與配置文件對照關系
MANIFEST.MF(Overview,Dependencies)
plugin.xml(Extension)
build.properties(Build)
plugin.xml源碼
[html]
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
<extension
point="org.eclipse.ui.commands">
<category
name="Touchit Category"
id="info.woody.tool.touchit.commands.category">
</category>
<command
name="info.woody.tool.touchit"
categoryId="info.woody.tool.touchit.commands.category"
id="info.woody.tool.touchit.commands.touchCommand">
</command>
</extension>
<extension
point="org.eclipse.ui.handlers">
<handler
commandId="info.woody.tool.touchit.commands.touchCommand"
class="info.woody.tool.touchit.handlers.TouchHandler">
</handler>
</extension>
<extension
point="org.eclipse.ui.bindings">
<key
commandId="info.woody.tool.touchit.commands.touchCommand"
contextId="org.eclipse.ui.contexts.window"
sequence="M1+0"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
</key>
</extension>
<extension
point="org.eclipse.ui.menus">
<!--
<menuContribution
locationURI="menu:org.eclipse.ui.main.menu?after=additions">
<menu
label="Sample Menu"
mnemonic="M"
id="info.woody.tool.touchit.menus.touchMenu">
<command
commandId="info.woody.tool.touchit.commands.touchCommand"
mnemonic="S"
id="info.woody.tool.touchit.menus.touchCommand">
</command>
</menu>
</menuContribution>
-->
<menuContribution
locationURI="toolbar:org.eclipse.ui.main.toolbar?after=additions">
<toolbar
id="info.woody.tool.touchit.toolbars.touchToolbar">
<command
commandId="info.woody.tool.touchit.commands.touchCommand"
icon="icons/touchjsp.jpeg"
tooltip="Touch JSP within selected files/folders"
id="info.woody.tool.touchit.toolbars.touchCommand">
</command>
</toolbar>
</menuContribution>
</extension>
</plugin>
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
<extension
point="org.eclipse.ui.commands">
<category
name="Touchit Category"
id="info.woody.tool.touchit.commands.category">
</category>
<command
name="info.woody.tool.touchit"
categoryId="info.woody.tool.touchit.commands.category"
id="info.woody.tool.touchit.commands.touchCommand">
</command>
</extension>
<extension
point="org.eclipse.ui.handlers">
<handler
commandId="info.woody.tool.touchit.commands.touchCommand"
class="info.woody.tool.touchit.handlers.TouchHandler">
</handler>
</extension>
<extension
point="org.eclipse.ui.bindings">
<key
commandId="info.woody.tool.touchit.commands.touchCommand"
contextId="org.eclipse.ui.contexts.window"
sequence="M1+0"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
</key>
</extension>
<extension
point="org.eclipse.ui.menus">
<!--
<menuContribution
locationURI="menu:org.eclipse.ui.main.menu?after=additions">
<menu
label="Sample Menu"
mnemonic="M"
id="info.woody.tool.touchit.menus.touchMenu">
<command
commandId="info.woody.tool.touchit.commands.touchCommand"
mnemonic="S"
id="info.woody.tool.touchit.menus.touchCommand">
</command>
</menu>
</menuContribution>
-->
<menuContribution
locationURI="toolbar:org.eclipse.ui.main.toolbar?after=additions">
<toolbar
id="info.woody.tool.touchit.toolbars.touchToolbar">
<command
commandId="info.woody.tool.touchit.commands.touchCommand"
icon="icons/touchjsp.jpeg"
tooltip="Touch JSP within selected files/folders"
id="info.woody.tool.touchit.toolbars.touchCommand">
</command>
</toolbar>
</menuContribution>
</extension>
</plugin>build.properties源碼
[plain]
source.. = src/
output.. = bin/
bin.includes = plugin.xml,\
META-INF/,\
.,\
icons/
source.. = src/
output.. = bin/
bin.includes = plugin.xml,\
META-INF/,\
.,\
icons/Java程序
方法execute是插件執行時候的入口方法,我們會根據View類型來選擇是否執行後續邏輯,如果當前活動View為ResourceNavigator/ProjectExplorer/PackageExplorer,那麼我們就會根據View裡選中的資源節點來查找JSP文件,如果不是這三種View,後續代碼就沒有必要執行了。利用Alt+Shift+F1可以找出我們當前的活動View,這個功能有點類似與Visual Studio中的工具Spy++。
[java]
if (activePage.isPartVisible(viewPart)) {
if ("org.eclipse.ui.views.ResourceNavigator".equals(viewPart.getViewSite().getId())) {
break;
}
if ("org.eclipse.ui.navigator.ProjectExplorer".equals(viewPart.getViewSite().getId())) {
break;
}
if ("org.eclipse.jdt.ui.PackageExplorer".equals(viewPart.getViewSite().getId())) {
break;
}
}
if (activePage.isPartVisible(viewPart)) {
if ("org.eclipse.ui.views.ResourceNavigator".equals(viewPart.getViewSite().getId())) {
break;
}
if ("org.eclipse.ui.navigator.ProjectExplorer".equals(viewPart.getViewSite().getId())) {
break;
}
if ("org.eclipse.jdt.ui.PackageExplorer".equals(viewPart.getViewSite().getId())) {
break;
}
}余下的邏輯就是循環遍歷選中的那些資源節點以及資源節點子目錄中的JSP文件,然後更新文件的最後修改時間屬性。具體的內部調用邏輯如下:
源碼如下:
[java]
package info.woody.tool.touchit.handlers;
import java.io.File;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IViewReference;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
public class TouchHandler extends AbstractHandler {
public Object execute(ExecutionEvent event) throws ExecutionException {
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench == null ? null : workbench.getActiveWorkbenchWindow();
IWorkbenchPage activePage = window == null ? null : window.getActivePage();
IViewPart viewPart = null;
for (IViewReference viewRef : activePage.getViewReferences()) {
// only work with visible one
viewPart = viewRef.getView(false);
if (viewPart == null) {
continue;
}
if (activePage.isPartVisible(viewPart)) {
if ("org.eclipse.ui.views.ResourceNavigator".equals(viewPart.getViewSite().getId())) {
break;
}
if ("org.eclipse.ui.navigator.ProjectExplorer".equals(viewPart.getViewSite().getId())) {
break;
}
if ("org.eclipse.jdt.ui.PackageExplorer".equals(viewPart.getViewSite().getId())) {
break;
}
}
viewPart = null;
}
if (viewPart == null) {
return null;
}
List<?> files = ((ITreeSelection)viewPart.getSite().getSelectionProvider().getSelection()).toList();
Set<IResource> pathSet = scanSelectedFiles(files);
startTouchJob(pathSet);
return null;
}
/**
* Start a job to execute "Touch" JSP files in order to refresh the last modified date time
*
* @param pathSet
*/
private void startTouchJob(final Set<IResource> pathSet) {
if (null == pathSet || 0 == pathSet.size()) {
return;
}
// http://eclipse-tips.com/how-to-guides/4-progress-bars-in-eclipse-ui?showall=1
Job job = new Job("Touch files") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
monitor.beginTask("Touching files ...", pathSet.size());
int workedCount = 0;
for (IResource resource : pathSet) {
try {
clearInternalError(resource);
if (!new File(resource.getRawLocation().toOSString()).setLastModified(new Date().getTime())) {
saveInternalError(resource);
}
resource.refreshLocal(IResource.DEPTH_ZERO, null);
} catch (Exception e) {
saveInternalError(resource);
}
monitor.worked(++workedCount);
}
} finally {
monitor.worked(pathSet.size());
monitor.done();
}
return Status.OK_STATUS;
}
};
job.schedule();
}
/**
* Scan all path given in parameter files to find JSP files
*
* @param files
* @return
*/
private Set<IResource> scanSelectedFiles(List<?> files) {
final Set<IResource> pathSet = new HashSet<IResource>();
for (Object resource : files) {
if (resource instanceof IResource) {
this.savePath((IResource)resource, pathSet);
}
}
return pathSet;
}
/**
* Accept a JSP file or a directory and save all JSP file paths to parameter "pathSet"
*
* @param resource
* @param pathSet
*/
private void savePath(IResource resource, Set<IResource> pathSet) {
if (isJsp(resource)) {
pathSet.add(resource);
return;
} else if (!(resource instanceof IFolder)) {
return;
}
// http://stackoverflow.com/questions/6776252/non-recursive-way-to-get-all-files-in-a-directory-and-its-subdirectories-in-java
Stack<IResource> stack = new Stack<IResource>();
try {
stack.push(resource);
while (!stack.isEmpty()) {
IResource child = stack.pop();
if (child instanceof IFolder) {
stack.addAll(Arrays.asList(((IFolder) child).members()));
} else if (isJsp(child)) {
pathSet.add(child);
}
}
} catch (CoreException e) {
}
}
/**
*
* @param resource
* @return
*/
private boolean isJsp(IResource resource) {
return resource instanceof IFile && "jsp".equalsIgnoreCase(resource.getFileExtension());
}
/**
* Clear markers in [Problem] view those are marked by Touchit plugin
*
* @param resource
*/
private void clearInternalError(IResource resource) {
try {
IMarker[] markers = resource.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
if (null == markers || 0 == markers.length) {
return;
}
for (IMarker eachMarker : markers) {
if ("File cannot be touched.".equals(eachMarker.getAttribute(IMarker.MESSAGE))) {
eachMarker.delete();
}
}
} catch (CoreException e) {
}
}
/**
* Save a marker in [Problem] view
*
* @param resource
*/
private void saveInternalError(IResource resource) {
try {
IMarker marker = resource.createMarker(IMarker.PROBLEM);
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.MESSAGE, "File cannot be touched.");
marker.setAttribute(IMarker.LOCATION, resource.getRawLocation().toOSString());
} catch (CoreException e) {
}
}