Mono Developer 在中文 Mac OS 下的亂碼解決方式

今天在 Mac 下試裝了 Mono 開發環境,包括了 Mono Framework 與 Mono Developer,不過 Mono Developer 跑在中文 Mac OS 下會出現亂碼,參考了中文Mac系统下MonoDevelop乱码解决一文,發現應該是 Mono Developer 的中文語系有問題,整理解決步驟如下,細部的操作圖解,可以上面提出的參考連結︰
  • 在 Mono Developer 程式上,按下滑鼠右鍵,選擇「顯示套件內容」
  • 移除套件內容目錄下的 Contents/MacOS/share/locale/zh_TW 目錄
記錄一下。

Intellij IDEA color scheme 資源整理

最近常使用 Intellij IDEA IDE 來寫 Java 和 Scala 的程式,整理一些我覺得好看的配色主題如下︰

YNDict 程式改版

時間過真快,想不到距離我寫出第一版 YNDict 的時間都已經過 2 年多了……。這 2 年下來,其實一直有想要再改進一下 YNdict 的程式,但因為一直有其他的事在忙 (講實話就是一個懶 orz),加上功能好像也還可以動,所以就一直放著不管。

最近因為 Yahoo 字典改版,加上又放了個假,所以今天下午想說要重新改一下 YNDict,當做放假後的收心操,結果就是上方抓圖的樣子了。

功能上,除了版面因應 Yahoo 字典功能改版,有做了一些微調之外,其他使用方式應該和之前寫的這篇使用說明差不多,請大家參照服用即可。

至於程式的下載位址,和之前的連結一模一樣,至於新版的 source code,因為這兩天可能還會微調一下程式,待穩定後應該一樣會放出來,版權沒有,有興趣的朋友一樣可以自取自用,不用告知我,謝謝。

[心得] 在無法對外連線的環境下使用 dom4j 時,記得關閉 DTD 驗証功能

花了我一兩個小時在排除這問題……,記錄一下。

上禮拜在客戶的環境下測試一隻程式時,發現程式會卡住不動,一開始我還以為,也許是程式要讀取的某個 XML 檔被其他的程式 lock 住了,才會讀不出來。後來耐心的等程式把 exception 吐出來,才發現問題出在 dom4j 上頭。

因為 dom4j 在解析 XML 檔時,也會一併去抓取該 XML 所指定的 DTD 檔下來做驗証,但因為客戶機房的環境是不能對外的,所以 dom4j 在取不到 DTD 文件後,就會吐出 exception,程式就會失效了。

解決辦法也很簡單,在使用 SAXReader 讀取 XML 檔之前,記得先將 SAXReader 的 XML DTD 驗証功能關掉就可以了,如下︰

SAXReader saxReader = new SAXReader();
// 以下兩行其實是相同的東西;
// saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
saxReader.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.LOAD_EXTERNAL_DTD_FEATURE, false);  

Struts 2 下在 View Layer 新增分頁 (Pager) tag 與相關設計分享

分頁是大家撰寫網頁時,常需實作的功能。最近因為有需要,所以在 Struts 2 下的實作出了個 Pager tag,用以方便分頁套版使用,我自己用起來是用得蠻開心的,就想說分享出來給大家看看。

因為加班好累,所以說明可能晚點待補,有需要的話,或許晚點會直接打包分享出來?另外再來有時間也會再補充說明 Struts 2 下的 tag 撰寫方式,做個小小的心得分享,

老王賣瓜一下,我自己覺得我寫的 Pager tag,有以下兩個優點︰

  1. 簡化套版-這是廢話 XD 主要應該算容易使用,下面我們會看到結果、
  2. 利用到 MVC 的特性,修改版面配置 (不只 CSS) 不需修改 Java 程式,只需修改 template 檔即可。

成果圖如下︰

Tag 設定︰Pager.tld

<?xml version="1.0" encoding="UTF-8"?>
<?XML:NAMESPACE PREFIX = [default] http://java.sun.com/xml/ns/j2ee NS = "http://java.sun.com/xml/ns/j2ee" /><?XML:NAMESPACE PREFIX = [default] http://java.sun.com/xml/ns/j2ee NS = "http://java.sun.com/xml/ns/j2ee" /><?XML:NAMESPACE PREFIX = [default] http://java.sun.com/xml/ns/j2ee NS = "http://java.sun.com/xml/ns/j2ee" /><?XML:NAMESPACE PREFIX = [default] http://java.sun.com/xml/ns/j2ee NS = "http://java.sun.com/xml/ns/j2ee" /><?XML:NAMESPACE PREFIX = [default] http://java.sun.com/xml/ns/j2ee NS = "http://java.sun.com/xml/ns/j2ee" /><?XML:NAMESPACE PREFIX = [default] http://java.sun.com/xml/ns/j2ee NS = "http://java.sun.com/xml/ns/j2ee" /><?XML:NAMESPACE PREFIX = [default] http://java.sun.com/xml/ns/j2ee NS = "http://java.sun.com/xml/ns/j2ee" /><?XML:NAMESPACE PREFIX = [default] http://java.sun.com/xml/ns/j2ee NS = "http://java.sun.com/xml/ns/j2ee" />
    分頁導覽器
    2.0
    pager
    /pager

    
        pager
        com.foo.tags.PagerTag
        JSP
        
        
            currentPage
            true
        
        
        
            totalRecordNumber
            true
        
        
        
        	actionUrl
            true
        
        
               
      		theme       
      		false       
      		false     
      		  
	    
	    
               
      		template       
      		false       
      		false
      		
	    
    

View Layer template 設定︰pager.ftl

	
	<@s.if test="${parameters.currentPage} != 1">
		
		<@s.if test="${parameters.actionUrl.lastIndexOf('?')} != -1">
			<@s.a href="${parameters.actionUrl}¤tPage=${parameters.currentPage-1}" title="上一頁">上一頁</@S.A> |
		</@S.IF>
		<@s.else>
			<@s.a href="${parameters.actionUrl}?currentPage=${parameters.currentPage-1}" title="上一頁">上一頁</@S.A> |
		</@S.ELSE>		
	</@S.IF>

	
	<@s.if test="${parameters.currentPage} != 1 && (${parameters.currentPage} - 5) >= 1">
		<@s.if test="${parameters.actionUrl.lastIndexOf('?')} != -1">
			<@s.a href="${parameters.actionUrl}¤tPage=${parameters.currentPage-5}" title="上五頁">...</@S.A>
		</@S.IF>
		<@s.else>
			<@s.a href="${parameters.actionUrl}?currentPage=${parameters.currentPage-5}" title="上五頁">...</@S.A>
		</@S.ELSE>
	</@S.IF>

	
	<@s.iterator value="totalRecordNumber.{#this}" id="pages" status="statPage">
		
		<@s.if test="${parameters.currentPage} == #statPage.count">
			<@s.property value="#statPage.count" />
		</@S.IF>
		
		<@s.else>
			
			<@s.if test="#statPage.count >= (${parameters.currentPage} - 4) && #statPage.count <= (${parameters.currentPage} + 4)">
				
				<@s.if test="#statPage.count <= ${parameters.totalRecordNumber}">
					<@s.if test="${parameters.actionUrl.lastIndexOf('?')} != -1">
						<@s.a href="${parameters.actionUrl}¤tPage=${statPage.count}" title="第${statPage.count}頁"><@s.property value="#statPage.count" /></@S.A>
					</@S.IF>
					<@s.else>
						<@s.a href="${parameters.actionUrl}?currentPage=${statPage.count}" title="第${statPage.count}頁"><@s.property value="#statPage.count" /></@S.A>
					</@S.ELSE>	
				</@S.IF>
			</@S.IF>
		</@S.ELSE>
	</@S.ITERATOR>
			
	
	<@s.if test="${parameters.currentPage} != ${parameters.totalRecordNumber} && (${parameters.totalRecordNumber} - ${parameters.currentPage}) >= 5">
		<@s.if test="${parameters.actionUrl.lastIndexOf('?')} != -1">
			<@s.a href="${parameters.actionUrl}¤tPage=${parameters.currentPage+5}" title="下五頁">...</@S.A>
		</@S.IF>
		<@s.else>
			<@s.a href="${parameters.actionUrl}?currentPage=${parameters.currentPage+5}" title="下五頁">...</@S.A>
		</@S.ELSE>
	</@S.IF>

	
	<@s.if test="${parameters.currentPage} != ${parameters.totalRecordNumber}">
		<@s.if test="${parameters.actionUrl.lastIndexOf('?')} != -1">
			| <@s.a href="${parameters.actionUrl}¤tPage=${parameters.currentPage+1}" title="下一頁">下一頁</@S.A>
		</@S.IF>
		<@s.else>
			| <@s.a href="${parameters.actionUrl}?currentPage=${parameters.currentPage+1}" title="下一頁">下一頁</@S.A>
		</@S.ELSE>
	</@S.IF>

Tag Java code︰Pager.java

import com.opensymphony.xwork2.util.ValueStack;
import org.apache.struts2.components.UIBean;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@StrutsTag(name = "pager", tldTagClass = "com.foo.tags.PagerTag", description = "Pager")
public class Pager extends UIBean {
	public final static String TEMPLATE = "pager";
	private static final Integer PAGESIZE = 10;  // 

	private String currentPage;			// 查詢頁數
	private String totalRecordNumber;	// 全部筆數
	private String actionUrl;			// 目標路徑
	private String template;			// 樣板名稱

	public Pager(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
		super(stack, request, response);
	}

	@Override
	protected String getDefaultTemplate() {
		if (getTemplate() != null && getTemplate().length() != 0) {
			return getTemplate();
		} else {
			return TEMPLATE;
		}
	}

	@StrutsTagAttribute(description = "set currentPage", type = "String")
	public void setCurrentPage(String currentPage) {
		this.currentPage = currentPage;
	}

	@StrutsTagAttribute(description = "set totalRecordNumber", type = "String")
	public void setTotalRecordNumber(String totalRecordNumber) {
		this.totalRecordNumber = totalRecordNumber;
	}

	@StrutsTagAttribute(description = "set template", type = "String")
	public void setTemplate(String template) {
		this.template = template;
	}

	public String getTemplate() {
		return template;
	}

	@StrutsTagAttribute(description = "set actionUrl", type = "String")
	public void setActionUrl(String actionUrl) {
		this.actionUrl = actionUrl;
	}

	@Override
	protected void evaluateExtraParams() {
		super.evaluateExtraParams();

		if (null != currentPage && null != totalRecordNumber && null != actionUrl) {
			// 查詢頁數
			Integer currentPageInt = (Integer) findValue(currentPage);
			// 全部頁數
			Integer totalRecordNumberPages = (Integer) findValue(totalRecordNumber);
			totalRecordNumberPages = (totalRecordNumberPages - 1) / PAGESIZE + 1;
			// 目標路徑
			String actionUrlStr = (String) findValue("actionUrl");

			addParameter("currentPage", currentPageInt);
			addParameter("totalRecordNumber", totalRecordNumberPages);
			addParameter("actionUrl", actionUrlStr);
		}
	}

Tag Java code︰PagerTag.java

import org.apache.struts2.views.jsp.ui.AbstractUITag;
import org.apache.struts2.components.Component;
import com.opensymphony.xwork2.util.ValueStack;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PagerTag extends AbstractUITag {
	private static final long serialVersionUID = 1L;

	private String currentPage;			// 查詢頁數
	private String totalRecordNumber;	// 全部筆數
	private String actionUrl;			// 目標路徑
	private String template;			// 樣板名稱


    @Override
    public Component getBean(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
        return new Pager(stack, request, response);
    }

    @Override
    protected void populateParams() {
        super.populateParams();

        Pager pager = (Pager)component;
        pager.setCurrentPage(currentPage);
        pager.setTotalRecordNumber(totalRecordNumber);
        pager.setActionUrl(actionUrl);
        pager.setTemplate(template);
    }

    public void setCurrentPage(String currentPage) {
        this.currentPage = currentPage;
    }
    
    public void setTotalRecordNumber(String totalRecordNumber){
    	this.totalRecordNumber = totalRecordNumber;
    }
    
    public void setActionUrl(String actionUrl){
    	this.actionUrl = actionUrl;
    }
    
    public void setTemplate(String template){
    	this.template = template;
    }
}

Tag Java code︰PagedBaseAction.java

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class PagedBaseAction extends BaseAction {
	private static final Log logger = LogFactory.getLog(PagedBaseAction.class);

	protected int totalPageNumber = 0;		// 總頁數
	protected int totalRecordNumber = 0;	// 結果總筆數
	protected int currentPage = 1;			// 目前觀看頁數
	protected int firstRowIndex = 0;		// 本頁開始筆數
	protected int endRowIndex = 0;			// 本頁結束筆數

	// getters and setters - begin
	public int getTotalPageNumber() {
		return totalPageNumber;
	}

	public void setTotalPageNumber(int totalPageNumber) {
		this.totalPageNumber = totalPageNumber;
	}

	public int getTotalRecordNumber() {
		return totalRecordNumber;
	}

	public void setTotalRecordNumber(int totalRecordNumber) {
		this.totalRecordNumber = totalRecordNumber;
	}

	public int getCurrentPage() {
		return currentPage;
	}

	public void setCurrentPage(int currentPage) {
		this.currentPage = currentPage;
	}

	public int getFirstRowIndex() {
		return firstRowIndex;
	}

	public void setFirstRowIndex(int firstRowIndex) {
		this.firstRowIndex = firstRowIndex;
	}

	public int getEndRowIndex() {
		return endRowIndex;
	}

	public void setEndRowIndex(int endRowIndex) {
		this.endRowIndex = endRowIndex;
	}
	// getters and setters - end

	protected void pagedProcess(int pageSize) {
		logger.info("pagedProcess - start");

		if (totalRecordNumber == 0) {
			this.totalPageNumber = 0;
			this.firstRowIndex = -1;
			this.endRowIndex = -1;
		} else {
			this.totalPageNumber = (totalRecordNumber - 1) / pageSize + 1;	// 總頁數
			this.firstRowIndex = (pageSize * (currentPage - 1));			// 本頁開始筆數
			this.endRowIndex = ((firstRowIndex + pageSize + 1) > totalRecordNumber) ? totalRecordNumber - 1 : (firstRowIndex + pageSize - 1); // 本頁結束筆數
		}

		logger.debug(String.format("pagedProcess - totalRecordNumber=%d, totalPageNumber=%d, firstRowIndex=%d, endRowIndex=%d", totalRecordNumber, totalPageNumber, firstRowIndex, endRowIndex));
		
		logger.info("pagedProcess - end");
	}
}

使用方式 (對,以上面只是基礎建設 XD)︰

1. Controlerr 下,新增一個 Struts 2 的 Action,來繼承 PagedBaseAction 此分頁基底類別,舉例如下︰

public class PagedAction extends PagedBasedAction {

    public string execute() {
        totalRecordNumber = 100;    // Model 資料總筆數
        super.pagedProcess(10);	    // 每 10 頁一筆
        return SUCCESS;
    }
}

2. View Layer 下,使用如下方式套版,即可顯示分頁導航列了︰



    


是不是很簡單呢?XD

在 ASP.Net MVC 的自訂 route 下,針對 favicon.ico 檔特別處理

最近因為特殊需求,需要在 Global.asax.cs 下新增一筆特殊的 route rule 如下︰

routes.MapRoute(
    "Default",                                         
    "{moduleName}/{controller}/{action}",             
    new { controller = "Home", action = "Index", moduleName = "" }
);

原本運作得不錯,但在新增 favicon.ico 檔設定後,突然發現在 ASP.Net MVC 下,對 favicon.ico 的 request 不像 .jpg, .png 等圖檔或 .css 檔案一樣被正常存取,而是會被當做對 Controller 的 request 而進入 MVC 的執行流程中,即參數 moduleName 會被代入 favicon.ico 這個值造成網站爆炸了這樣。

解決方式其實也很簡單,google 了一下後,發現在 Global.asax.cs 中再加上如下的設定,讓 MVC 的 Route Table 跳過對 favicon.ico 的 request 就可以了。

routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" });

參考網址︰

  1. ASP.NET MVC: Ignore requests to favicon.ico
  2. Ignoring Favicon.ico in ASP.Net MVC

Struts 2 學習資源整理

最近因為工作的緣故,正在學習 Struts 2 Web Framework,本篇文章整理我學習 Struts 2 的一些資源,以後有看到或是讀到新的資源,會陸續再更新本篇文章。

書 (紙本書 or 電子書)︰

影片︰

網站、文件︰