Java tutorial
/* * Copyright 2004-2005 the original author or authors. * * Licensed 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.codehaus.groovy.grails.web.pages; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.groovy.grails.commons.*; import org.codehaus.groovy.grails.web.taglib.GrailsTagRegistry; import org.codehaus.groovy.grails.web.taglib.GroovySyntaxTag; import org.codehaus.groovy.grails.web.taglib.exceptions.GrailsTagException; import java.io.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * NOTE: Based on work done by the GSP standalone project (https://gsp.dev.java.net/) * * Parsing implementation for GSP files * * @author Troy Heninger * @author Graeme Rocher * * Date: Jan 10, 2004 * */ public class Parse implements Tokens { public static final Log LOG = LogFactory.getLog(Parse.class); private static final Pattern PARA_BREAK = Pattern.compile("/p>\\s*<p[^>]*>", Pattern.CASE_INSENSITIVE); private static final Pattern ROW_BREAK = Pattern.compile("((/td>\\s*</tr>\\s*<)?tr[^>]*>\\s*<)?td[^>]*>", Pattern.CASE_INSENSITIVE); private static final Pattern PARSE_TAG_FIRST_PASS = Pattern .compile("(\\s*(\\S+)\\s*=\\s*[\"]([^\"]*)[\"][\\s|>]{1}){1}"); private static final Pattern PARSE_TAG_SECOND_PASS = Pattern .compile("(\\s*(\\S+)\\s*=\\s*[']([^']*)['][\\s|>]{1}){1}"); private Scan scan; private GSPWriter out; private String className; private boolean finalPass = false; private int tagIndex; private Map tagContext; private List tagMetaStack = new ArrayList(); private GrailsTagRegistry tagRegistry = GrailsTagRegistry.getInstance(); private boolean bufferWhiteSpace; private StringBuffer whiteSpaceBuffer = new StringBuffer(); private int currentOutputLine = 1; private String contentType = DEFAULT_CONTENT_TYPE; private boolean doNextScan = true; private int state; private static final String START_MULTILINE_STRING = "'''"; private static final String END_MULTILINE_STRING = "'''"; private static final String DEFAULT_CONTENT_TYPE = "text/html;charset=UTF-8"; private Map constants = new TreeMap(); private int constantCount = 0; private final String pageName; private static final String EMPTY_MULTILINE_STRING = "''''''"; public static final String[] DEFAULT_IMPORTS = new String[] { "org.codehaus.groovy.grails.web.pages.GroovyPage", "org.codehaus.groovy.grails.web.taglib.*", "org.springframework.web.util.*", "grails.util.GrailsUtil" }; private static final String CONFIG_PROPERTY_DEFAULT_CODEC = "grails.views.default.codec"; private static final String CONFIG_PROPERTY_GSP_ENCODING = "grails.views.gsp.encoding"; private String codecName; private static final String IMPORT_DIRECTIVE = "import"; private static final String CONTENT_TYPE_DIRECTIVE = "contentType"; private static final String DEFAULT_CODEC_DIRECTIVE = "defaultCodec"; private String gspEncoding; public static final String GROOVY_SOURCE_CHAR_ENCODING = "UTF-8"; public String getContentType() { return this.contentType; } public int getCurrentOutputLineNumber() { return currentOutputLine; } class TagMeta { String name; String namespace; Object instance; boolean isDynamic; boolean hasAttributes; int lineNumber; public String toString() { return "<" + namespace + ":" + name + ">"; } } public Parse(String name, String filename, InputStream in) throws IOException { Map config = ConfigurationHolder.getFlatConfig(); // Get the GSP file encoding from Config, or fall back to system file.encoding if none set Object gspEnc = config.get(CONFIG_PROPERTY_GSP_ENCODING); if ((gspEnc != null) && (gspEnc.toString().trim().length() > 0)) { gspEncoding = gspEnc.toString(); } else { gspEncoding = System.getProperty("file.encoding", "us-ascii"); } if (LOG.isDebugEnabled()) { LOG.debug("GSP file encoding set to: " + gspEncoding); } scan = new Scan(readStream(in)); this.pageName = filename; makeName(name); Object o = config.get(CONFIG_PROPERTY_DEFAULT_CODEC); lookupCodec(o); } // Parse() private void lookupCodec(Object o) { if (o != null) { String codecName = o.toString(); GrailsApplication app = ApplicationHolder.getApplication(); if (app != null) { GrailsClass codecClass = app.getArtefactByLogicalPropertyName(CodecArtefactHandler.TYPE, codecName); if (codecClass == null) codecClass = app.getArtefactByLogicalPropertyName(CodecArtefactHandler.TYPE, codecName.toUpperCase()); if (codecClass != null) { this.codecName = codecClass.getFullName(); } } } } public int[] getLineNumberMatrix() { return out.getLineNumbers(); } public InputStream parse() { StringWriter sw = new StringWriter(); out = new GSPWriter(sw, this); page(); finalPass = true; scan.reset(); page(); // This gets bytes in system's default encoding InputStream in = null; try { in = new ByteArrayInputStream(sw.toString().getBytes(GROOVY_SOURCE_CHAR_ENCODING)); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Grails cannot run unless your environment supports UTF-8!"); } //System.out.println("Compiled GSP into Groovy code: " + sw.toString()); if (LOG.isDebugEnabled()) { LOG.debug("Compiled GSP into Groovy code: " + sw.toString()); } scan = null; return in; } private void declare(boolean gsp) { if (finalPass) return; if (LOG.isDebugEnabled()) LOG.debug("parse: declare"); out.println(); write(scan.getToken().trim(), gsp); out.println(); out.println(); } // declare() private void direct() { if (finalPass) return; if (LOG.isDebugEnabled()) LOG.debug("parse: direct"); String text = scan.getToken(); text = text.trim(); directPage(text); } // direct() private void directPage(String text) { text = text.trim(); // LOG.debug("directPage(" + text + ')'); Pattern pat = Pattern.compile("(\\w+)\\s*=\\s*\"([^\"]*)\""); Matcher mat = pat.matcher(text); for (int ix = 0;;) { if (!mat.find(ix)) return; String name = mat.group(1); String value = mat.group(2); if (name.equals(IMPORT_DIRECTIVE)) pageImport(value); if (name.equals(CONTENT_TYPE_DIRECTIVE)) contentType(value); if (name.equals(DEFAULT_CODEC_DIRECTIVE)) lookupCodec(value); ix = mat.end(); } } // directPage() private void contentType(String value) { this.contentType = value; } private void scriptletExpr() { if (!finalPass) return; if (LOG.isDebugEnabled()) LOG.debug("parse: expr"); String text = scan.getToken().trim(); out.printlnToResponse(text); } private void expr() { if (!finalPass) return; if (LOG.isDebugEnabled()) LOG.debug("parse: expr"); String text = scan.getToken().trim(); if (codecName != null) { out.printlnToResponse("Codec.encode(" + text + ")"); } else { out.printlnToResponse(text); } } // expr() private void html() { if (!finalPass) return; if (LOG.isDebugEnabled()) LOG.debug("parse: html"); String text = scan.getToken(); if (Pattern.compile("\\S").matcher(text).find()) bufferWhiteSpace = false; StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); String[] lines = text.split("\\n"); if (lines.length == 1 && !StringUtils.isBlank(lines[0])) { out.printlnToResponse('\'' + escapeGroovy(lines[0]) + '\''); } else { pw.print(START_MULTILINE_STRING); boolean hasContent = false; boolean firstLine = true; for (int i = 0; i < lines.length; i++) { String line = lines[i]; final String content = escapeGroovy(line); if (!StringUtils.isEmpty(content)) { if (!hasContent) { hasContent = true; break; } } } if (hasContent) { for (int i = 0; i < lines.length; i++) { String line = lines[i]; final String content = escapeGroovy(line); if (firstLine) { pw.print(content); firstLine = false; } else { pw.println(); pw.print(content); } } } pw.print(END_MULTILINE_STRING); pw.println(); if (hasContent && !bufferWhiteSpace) { final String constantValue = sw.toString(); final String constantName = "STATIC_HTML_CONTENT_" + constantCount++; constants.put(constantName, constantValue); out.printlnToResponse(constantName); } } } // html() private void makeName(String uri) { String name; int slash = uri.lastIndexOf('/'); if (slash > -1) { name = uri.substring(slash + 1); uri = uri.substring(0, (uri.length() - 1) - name.length()); while (uri.endsWith("/")) { uri = uri.substring(0, uri.length() - 1); } slash = uri.lastIndexOf('/'); if (slash > -1) { name = uri.substring(slash + 1) + '_' + name; } } else { name = uri; } StringBuffer buf = new StringBuffer(name.length()); for (int ix = 0, ixz = name.length(); ix < ixz; ix++) { char c = name.charAt(ix); if (c < '0' || (c > '9' && c < '@') || (c > 'Z' && c < '_') || (c > '_' && c < 'a') || c > 'z') c = '_'; else if (ix == 0 && c >= '0' && c <= '9') c = '_'; buf.append(c); } className = buf.toString(); } // makeName() private static boolean match(CharSequence pat, CharSequence text, int start) { int ix = start, ixz = text.length(), ixy = start + pat.length(); if (ixz > ixy) ixz = ixy; if (pat.length() > ixz - start) return false; for (; ix < ixz; ix++) { if (Character.toLowerCase(text.charAt(ix)) != Character.toLowerCase(pat.charAt(ix - start))) { return false; } } return true; } // match() private static int match(Pattern pat, CharSequence text, int start) { Matcher mat = pat.matcher(text); if (mat.find(start) && mat.start() == start) { return mat.end(); } return 0; } // match() private void page() { if (LOG.isDebugEnabled()) LOG.debug("parse: page"); if (finalPass) { out.println(); out.print("class "); out.print(className); out.println(" extends GroovyPage {"); out.println("public Object run() {"); } loop: for (;;) { if (doNextScan) state = scan.nextToken(); else doNextScan = true; switch (state) { case EOF: break loop; case HTML: html(); break; case JEXPR: scriptletExpr(); break; case JSCRIPT: script(false); break; case JDIRECT: direct(); break; case JDECLAR: declare(false); break; case GEXPR: expr(); break; case GSCRIPT: script(true); break; case GDIRECT: direct(); break; case GDECLAR: declare(true); break; case GSTART_TAG: startTag(); break; case GEND_TAG: endTag(); break; } } if (finalPass) { if (!tagMetaStack.isEmpty()) { TagMeta tag = (TagMeta) tagMetaStack.iterator().next(); throw new GrailsTagException( "Grails tags were not closed! [" + tagMetaStack + "] in GSP " + pageName + "", pageName, tag.lineNumber); } out.println("}"); for (Iterator i = constants.keySet().iterator(); i.hasNext();) { String name = (String) i.next(); out.println("static final " + name + " = " + constants.get(name)); } out.println("}"); } else { for (int i = 0; i < DEFAULT_IMPORTS.length; i++) { out.print("import "); out.println(DEFAULT_IMPORTS[i]); } if (codecName != null) { out.print("import "); out.print(codecName); out.println(" as Codec"); } } } // page() private void endTag() { if (!finalPass) return; String tagName = scan.getToken().trim(); String ns = scan.getNamespace(); if (tagMetaStack.isEmpty()) throw new GrailsTagException("Found closing Grails tag with no opening [" + tagName + "]"); TagMeta tm = (TagMeta) tagMetaStack.remove(this.tagMetaStack.size() - 1); String lastInStack = tm.name; String lastNamespaceInStack = tm.namespace; // if the tag name is blank then it has been closed by the start tag ie <tag /> if (StringUtils.isBlank(tagName)) tagName = lastInStack; if (!lastInStack.equals(tagName) || !lastNamespaceInStack.equals(ns)) { throw new GrailsTagException( "Grails tag [" + lastNamespaceInStack + ":" + lastInStack + "] was not closed"); } if (GroovyPage.DEFAULT_NAMESPACE.equals(ns) && tagRegistry.isSyntaxTag(tagName)) { if (tm.instance instanceof GroovySyntaxTag) { GroovySyntaxTag tag = (GroovySyntaxTag) tm.instance; if (tag.isBufferWhiteSpace()) bufferWhiteSpace = true; tag.doEndTag(); } else { throw new GrailsTagException("Grails tag [" + tagName + "] was not closed"); } } else { out.println("}"); if (tm.hasAttributes) { out.println("invokeTag('" + tagName + "','" + ns + "',attrs" + tagIndex + ",body" + tagIndex + ")"); } else { out.println("invokeTag('" + tagName + "','" + ns + "',[:],body" + tagIndex + ")"); } } tagIndex--; } private void startTag() { if (!finalPass) return; tagIndex++; String text; StringBuffer buf = new StringBuffer(scan.getToken().trim()); String ns = scan.getNamespace(); state = scan.nextToken(); while (state != HTML && state != GEND_TAG && state != EOF) { if (state == GTAG_EXPR) { buf.append("${"); buf.append(scan.getToken().trim()); buf.append("}"); } else { buf.append(scan.getToken().trim()); } state = scan.nextToken(); } doNextScan = false; text = buf.toString(); String tagName; Map attrs = new TreeMap(); text = text.replaceAll("[\r\n\t]", " "); // this line added TODO query this if (text.indexOf(' ') > -1) { // ignores carriage returns and new lines int i = text.indexOf(' '); tagName = text.substring(0, i); String attrTokens = text.substring(i, text.length()); attrTokens += '>'; // closing bracket marker // do first pass parse which retrieves double quoted attributes Matcher m = PARSE_TAG_FIRST_PASS.matcher(attrTokens); populateAttributesFromMatcher(m, attrs); // do second pass parse which retrieves single quoted attributes m = PARSE_TAG_SECOND_PASS.matcher(attrTokens); populateAttributesFromMatcher(m, attrs); } else { tagName = text; } if (state == EOF) { throw new GrailsTagException("Unexpected end of file encountered parsing Tag [" + tagName + "] for " + className + ". Are you missing a closing brace '}'?"); } TagMeta tm = new TagMeta(); tm.name = tagName; tm.namespace = ns; tm.hasAttributes = !attrs.isEmpty(); tm.lineNumber = getCurrentOutputLineNumber(); tagMetaStack.add(tm); if (GroovyPage.DEFAULT_NAMESPACE.equals(ns) && tagRegistry.isSyntaxTag(tagName)) { if (this.tagContext == null) { this.tagContext = new HashMap(); this.tagContext.put(GroovyPage.OUT, out); } GroovySyntaxTag tag = (GroovySyntaxTag) tagRegistry.newTag(tagName); tag.init(tagContext); tag.setAttributes(attrs); if (!tag.hasPrecedingContent() && !bufferWhiteSpace) { throw new GrailsTagException( "Tag [" + tag.getName() + "] cannot have non-whitespace characters directly preceding it."); } else if (!tag.hasPrecedingContent() && bufferWhiteSpace) { whiteSpaceBuffer.delete(0, whiteSpaceBuffer.length()); bufferWhiteSpace = false; } else { if (whiteSpaceBuffer.length() > 0) { out.printlnToResponse(whiteSpaceBuffer.toString()); whiteSpaceBuffer.delete(0, whiteSpaceBuffer.length()); } bufferWhiteSpace = false; } tag.doStartTag(); tm.instance = tag; } else { if (attrs.size() > 0) { out.print("attrs" + tagIndex + " = ["); for (Iterator i = attrs.keySet().iterator(); i.hasNext();) { String name = (String) i.next(); out.print(name); out.print(':'); out.print(attrs.get(name)); if (i.hasNext()) out.print(','); else out.println(']'); } } out.println("body" + tagIndex + " = new GroovyPageTagBody(this,binding.webRequest) {"); } } private void populateAttributesFromMatcher(Matcher m, Map attrs) { while (m.find()) { String name = m.group(2); String val = m.group(3); name = '\"' + name + '\"'; if (val.startsWith("${") && val.endsWith("}")) { val = val.substring(2, val.length() - 1); } else if (!(val.startsWith("[") && val.endsWith("]"))) { val = '\"' + val + '\"'; } attrs.put(name, val); } } private void pageImport(String value) { // LOG.debug("pageImport(" + value + ')'); String[] imports = Pattern.compile(";").split(value.subSequence(0, value.length())); for (int ix = 0; ix < imports.length; ix++) { out.print("import "); out.print(imports[ix]); out.println(); } } // pageImport() private String escapeGroovy(CharSequence text) { StringBuffer buf = new StringBuffer(); for (int ix = 0, ixz = text.length(); ix < ixz; ix++) { char c = text.charAt(ix); String rep = null; if (c == '\n') { incrementLineNumber(); rep = "\\n"; } else if (c == '\r') rep = "\\r"; else if (c == '\t') rep = "\\t"; else if (c == '\'') rep = "\\'"; else if (c == '\\') rep = "\\\\"; if (rep != null) buf.append(rep); else buf.append(c); } return buf.toString(); } private String readStream(InputStream in) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { byte[] buf = new byte[8192]; for (;;) { int read = in.read(buf); if (read <= 0) break; out.write(buf, 0, read); } return out.toString(gspEncoding); } finally { out.close(); in.close(); } } // readStream() private void script(boolean gsp) { if (!finalPass) return; if (LOG.isDebugEnabled()) LOG.debug("parse: script"); out.println(); write(scan.getToken().trim(), gsp); out.println(); out.println(); } // script() private void write(CharSequence text, boolean gsp) { if (!gsp) { out.print(text); return; } for (int ix = 0, ixz = text.length(); ix < ixz; ix++) { char c = text.charAt(ix); String rep = null; if (Character.isWhitespace(c)) { for (ix++; ix < ixz; ix++) { if (Character.isWhitespace(text.charAt(ix))) continue; ix--; rep = " "; break; } } else if (c == '&') { if (match(";", text, ix)) { rep = ";"; ix += 5; } else if (match("&", text, ix)) { rep = "&"; ix += 4; } else if (match("<", text, ix)) { rep = "<"; ix += 3; } else if (match(">", text, ix)) { rep = ">"; ix += 3; } } else if (c == '<') { if (match("<br>", text, ix) || match("<hr>", text, ix)) { rep = "\n"; incrementLineNumber(); ix += 3; } else { int end = match(PARA_BREAK, text, ix); if (end <= 0) end = match(ROW_BREAK, text, ix); if (end > 0) { rep = "\n"; incrementLineNumber(); ix = end; } } } if (rep != null) out.print(rep); else out.print(c); } } // write() private void incrementLineNumber() { currentOutputLine++; } } // Parse