GSPにpageEncodingディレクティブを作ってみた!

結局Parseクラスのパッチを作ってみた。最初にScanクラスでトークンを読み出してみて、pageEncodingディレクティブがあったらそれで再度Scanクラスのインスタンスを生成することにした。あんまり効率よくなさそう...
注)以下のパッチには不備があることが判明しています。詳細はここ

Index: Parse.java
===================================================================
--- Parse.java	(revision 4321)
+++ Parse.java	(working copy)
@@ -36,6 +36,7 @@
  *
  * Date: Jan 10, 2004
  *
+ * Added by pageEncoding directive by Norihiro Seto
  */
 public class Parse implements Tokens {
     public static final Log LOG = LogFactory.getLog(Parse.class);
@@ -60,6 +61,8 @@
     private String contentType = "text/html;charset=UTF-8";
     private boolean doNextScan = true;
     private int state;
+    private ByteArrayOutputStream streamContent;
+    private String pageEncoding;
 
 
     public String getContentType() {
@@ -80,6 +83,7 @@
 
     public Parse(String name, InputStream in) throws IOException {
         scan = new Scan(readStream(in));
+        scanPageEncoding();
         makeName(name);
     } // Parse()
 
@@ -129,6 +133,7 @@
             String value = mat.group(2);
             if (name.equals("import")) pageImport(value);
             if (name.equals("contentType")) contentType(value);
+            if (name.equals("pageEncoding")) pageEncoding(value);
             ix = mat.end();
         }
     } // directPage()
@@ -208,6 +213,50 @@
         return 0;
     } // match()
 
+    private void pageEncoding(String value) {
+    	this.pageEncoding = value;
+    }
+
+    private void scanPageEncoding() throws IOException {
+        if (LOG.isDebugEnabled()) LOG.debug("parse: scanPageEncoding");
+        int stateBkup = state;
+        boolean doNextScanBkup = doNextScan;
+        out = new GSPWriter(new StringWriter(), this);
+        try {
+	        loop: for (;;) {
+	            if(doNextScan)
+	                state = scan.nextToken();
+	            else
+	                doNextScan = true;
+	
+	            switch (state) {
+	                case EOF: break loop;
+	                case JDIRECT: direct(); break;
+	                case GDIRECT: direct(); break;
+	            }
+		        if (pageEncoding != null) {
+		            if (LOG.isDebugEnabled()) {
+		            	LOG.debug("parse: scanPageEncoding encoding="
+		            			+ pageEncoding);
+		            }
+		        	scan = new Scan(streamContent.toString(pageEncoding));
+		        	break;
+		        }
+	        }
+        }
+        finally {
+        	state = stateBkup;
+        	doNextScan = doNextScanBkup;
+        	streamContent = null;
+        	try {
+        		out.close();
+        	}
+        	catch (Exception ignore) {}
+        	out = null;
+        	scan.reset();
+        }
+    } // scanPageEncoding()
+    
     private void page() {
         if (LOG.isDebugEnabled()) LOG.debug("parse: page");
         if (finalPass) {
@@ -450,6 +499,7 @@
                 if (read <= 0) break;
                 out.write(buf, 0, read);
             }
+            streamContent = out;
             return out.toString();
         } finally {
             out.close();

テストクラスのパッチは、これ

Index: ParseTests.java
===================================================================
--- ParseTests.java	(revision 4321)
+++ ParseTests.java	(working copy)
@@ -82,6 +82,45 @@
  		assertEquals(trimAndRemoveCR(expected), trimAndRemoveCR(output));
  	}
 
+	public String parseCodeBytes(String uri, byte[] gsp) throws IOException {
+		StringWriter sw = new StringWriter();
+		PrintWriter pw = new PrintWriter(sw);
+		InputStream gspIn = new ByteArrayInputStream(gsp);
+        Parse parse = new Parse(uri, gspIn);
+        InputStream in = parse.parse();
+        send(in, pw);
+
+		return sw.toString();
+	}
+	
+ 	public void testParseWithPageEncoding() throws Exception {
+		String output = parseCodeBytes("myTest", (
+				"<%@ page pageEncoding=\"utf-8\" import=\"some.test.package.*\"%>" +
+				"<div>\u3053\u3093\u306b\u3061\u306f[Japanese]</div>"
+				).getBytes("utf-8"));
+		String expected = 
+			"import org.codehaus.groovy.grails.web.pages.GroovyPage\n" +
+			"import org.codehaus.groovy.grails.web.taglib.*\n"+
+			"import some.test.package.*\n" +
+			"\n"+
+			"class myTest extends GroovyPage {\n"+
+			"public Object run() {\n"+
+			"out.print('<div>\u3053\u3093\u306b\u3061\u306f[Japanese]</div>')\n"+
+			"}\n"+
+			"}";
+
+//		System.out.println(output);
+		assertEquals(expected, trimAndRemoveCR(output));
+		
+		String output2 = parseCodeBytes("myTest", (
+				"<%@ page import=\"some.test.package.*\"%><%@ page pageEncoding=\"utf-8\"%>" +
+				"<div>\u3053\u3093\u306b\u3061\u306f[Japanese]</div>"
+				).getBytes("utf-8"));
+		
+//		System.out.println(output2);
+		assertEquals(expected, trimAndRemoveCR(output2));
+ 	}
+
     /**
      * Copy all of input to output.
      * @param in

単体テストは成功することは確認した。実際にアプリケーションを動作してのテストはしていない。