001: /*
002: * DynamicPage.java
003: *
004: * Copyright (c) 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
005: *
006: * See the file "LICENSE.txt" for information on usage and redistribution
007: * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
008: */
009: package pnuts.servlet;
010:
011: import pnuts.lang.Context;
012: import pnuts.lang.Pnuts;
013: import pnuts.lang.Runtime;
014: import pnuts.lang.ParseException;
015: import pnuts.lang.PnutsException;
016: import pnuts.ext.CachedScript;
017: import pnuts.compiler.Compiler;
018: import java.io.*;
019: import java.util.StringTokenizer;
020: import java.net.URL;
021: import java.net.MalformedURLException;
022: import java.util.Set;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import org.pnuts.servlet.protocol.pea.Handler;
026:
027: /**
028: * A class responsible for dynamic page generation.
029: */
030: public class DynamicPage extends CachedScript {
031:
032: private final static int BUFFER_SIZE = 4096;
033: private final static String PRINT = "\nprint(";
034: private final static String EOL = "\")\n";
035:
036: public static void convert(File infile, File outfile, String enc,
037: Context context) throws IOException {
038: Reader in = null;
039: Writer out = null;
040:
041: try {
042: try {
043: if (enc == null) {
044: enc = context.getScriptEncoding();
045: }
046: if (enc != null) {
047: in = new InputStreamReader(new FileInputStream(
048: infile), enc);
049: out = new OutputStreamWriter(new FileOutputStream(
050: outfile), enc);
051: } else {
052: in = new FileReader(infile);
053: out = new FileWriter(outfile);
054: }
055: convert(in, out, infile.getAbsoluteFile()
056: .getParentFile().toURL(), enc, context);
057: } finally {
058: if (in != null) {
059: in.close();
060: }
061: }
062: } finally {
063: if (out != null) {
064: out.close();
065: }
066: }
067: }
068:
069: public static void convert(Reader input, Writer output,
070: URL baseloc, String enc, Context context)
071: throws IOException {
072: convert(input, output, baseloc, enc, context, null);
073: }
074:
075: public static void convert(Reader input, Writer output,
076: URL baseloc, String enc, Context context, Set scriptURLs)
077: throws IOException {
078: convert(input, output, baseloc, enc, context, scriptURLs,
079: new boolean[1]);
080: }
081:
082: static void convert(Reader input, Writer output, URL baseloc,
083: String enc, Context context, Set scriptURLs,
084: boolean[] escape) throws IOException {
085: char[] buf = new char[BUFFER_SIZE];
086: int state = 0; // 0==outside, 1==<, 2==<%, 3==<%?, 4==%
087: int type = -1; // 0=="<%-", 1=="<% ", 2=="<%=", 3=="<%@"
088: boolean printing = false;
089: CharArrayWriter s1 = new CharArrayWriter();
090: CharArrayWriter s2 = new CharArrayWriter();
091: int p1 = -1; // beginning of outer text
092: int p2 = -1;
093: boolean quote = false;
094:
095: int n;
096: while ((n = input.read(buf)) != -1) {
097: p1 = 0;
098: for (int i = 0; i < n; i++) {
099: char c = (char) buf[i];
100: switch (state) {
101: case 0: // outside
102: if (c == '<') {
103: state = 1;
104: }
105: break;
106: case 1: // <
107: if (c == '%') {
108: state = 2;
109: if (i - p1 >= 0) {
110: if (!printing) {
111: output.write(PRINT);
112: output.write('"');
113: printing = true;
114: } else {
115: if (quote) {
116: output.write('"');
117: }
118: output.write(',');
119: output.write('"');
120: }
121: quote = true;
122: if (s1.size() > 0) {
123: if (i - p1 > 1) {
124: s1.write(buf, p1, i - 1 - p1);
125: }
126: char[] ca = s1.toCharArray();
127: if (i == 0) {
128: encode(output, ca, 0, ca.length - 1);
129: } else {
130: encode(output, ca);
131: }
132: s1.reset();
133: } else {
134: if (i - p1 > 1) {
135: encode(output, buf, p1, i - 1 - p1);
136: }
137: }
138: }
139: continue;
140: } else {
141: state = 0;
142: }
143: break;
144: case 2:
145: if (c == '-') {
146: type = 0;
147: p2 = i + 1;
148: } else if (c == '=') {
149: type = 2;
150: p2 = i + 1;
151: } else if (c == '@') {
152: type = 3;
153: p2 = i + 1;
154: } else {
155: type = 1;
156: p2 = i;
157: }
158: state = 3;
159: break;
160: case 3:
161: if (c == '%') {
162: state = 4;
163: }
164: break;
165: case 4:
166: if (c == '>') {
167: state = 0;
168: if (i - p2 >= 1) {
169: if (i - p2 > 1) {
170: s2.write(buf, p2, i - 1 - p2);
171: }
172: if (type == 2) { // "<%="
173: if (!printing) {
174: output.write(PRINT);
175: printing = true;
176: } else {
177: if (quote) {
178: output.write('"');
179: }
180: output.write(',');
181: }
182: quote = false;
183: if (escape[0]) {
184: output.write("escape(");
185: }
186: s2.writeTo(output);
187: if (escape[0]) {
188: output.write(")");
189: }
190: } else if (type == 1) { // "<% "
191: if (printing) {
192: output.write(EOL);
193: printing = false;
194: }
195: s2.writeTo(output);
196: } else if (type == 3) { // "<%@"
197: if (printing) {
198: output.write(EOL);
199: printing = false;
200: }
201: char[] b = s2.toCharArray();
202: executeDirective(b, output, baseloc,
203: enc, context, scriptURLs,
204: escape);
205: output.write('\n');
206: } else if (type == 0) { // "<%-"
207: if (printing) {
208: output.write(EOL);
209: printing = false;
210: }
211: }
212: s2.reset();
213: }
214: p1 = i + 1;
215: p2 = -1;
216: type = -1;
217: } else {
218: state = 3;
219: }
220: break;
221: } // switch (state)
222: } // for
223:
224: if ((state == 0 || state == 1) && p1 >= 0) {
225: s1.write(buf, p1, n - p1);
226: p1 = 0;
227: } else if ((state == 2 || state == 3) && p2 >= 0) {
228: s2.write(buf, p2, n - p2);
229: p2 = 0;
230: } else if ((state == 4) && p2 >= 0) {
231: s2.write(buf, p2, n - p2 - 1);
232: p2 = 0;
233: }
234: }
235: if (state == 0 || state == 1) {
236: if (s1.size() > 0) {
237: if (!printing) {
238: output.write(PRINT);
239: output.write('"');
240: printing = true;
241: } else {
242: if (quote) {
243: output.write('"');
244: }
245: output.write(',');
246: output.write('"');
247: }
248: quote = true;
249: encode(output, s1.toCharArray());
250: }
251: if (printing) {
252: if (quote) {
253: output.write('"');
254: }
255: output.write(')');
256: }
257: } else if (s2.size() > 0) {
258: if (printing) {
259: output.write(EOL);
260: }
261: s2.writeTo(output);
262: if (quote) {
263: output.write('"');
264: }
265: output.write(')');
266: }
267: }
268:
269: /*
270: * <%@ include file=""%>
271: * <%@ include expr=""%>
272: */
273: static void executeDirective(char[] b, Writer output, URL baseloc,
274: String enc, Context context, Set scriptURLs,
275: boolean[] escape) throws IOException {
276: String s = new String(b);
277: StringTokenizer st = new StringTokenizer(s, " ");
278: String s1 = st.nextToken();
279: String s2 = null;
280: if (st.hasMoreTokens()) {
281: s2 = st.nextToken();
282: }
283: if ("include".equals(s1)) {
284: int idx = s2.indexOf('=');
285: if (idx < 0) {
286: throw new RuntimeException("corrupted directive");
287: }
288: String attr = s2.substring(0, idx).trim();
289: String value;
290: int len = s2.length();
291: if (s2.charAt(idx + 1) == '"' && s2.charAt(len - 1) == '"') {
292: value = s2.substring(6, len - 1);
293: } else {
294: value = s2.substring(5);
295: }
296: if ("file".equals(attr)) {
297: if (baseloc != null) {
298: URL url = new URL(baseloc, value);
299: if (scriptURLs != null) {
300: scriptURLs.add(url);
301: }
302: Reader reader;
303: if (enc != null) {
304: reader = new InputStreamReader(
305: url.openStream(), enc);
306: } else {
307: reader = Runtime.getScriptReader(url
308: .openStream(), context);
309: }
310: convert(reader, output, baseloc, enc, context,
311: scriptURLs, escape);
312: } else {
313: ClassLoader cl = Thread.currentThread()
314: .getContextClassLoader();
315: URL url = cl.getResource(value);
316: if (scriptURLs != null && url != null) {
317: scriptURLs.add(url);
318: }
319: InputStream in = null;
320: try {
321: if (url != null) {
322: in = url.openStream();
323: }
324: } catch (IOException e) {
325: // ignore
326: }
327: Reader reader;
328: if (in != null) {
329: if (enc != null) {
330: reader = new InputStreamReader(in, enc);
331: } else {
332: reader = Runtime.getScriptReader(in,
333: context);
334: }
335: convert(reader, output, null, enc, context,
336: scriptURLs, escape);
337: } else {
338: throw new FileNotFoundException(value);
339: }
340: }
341: } else if ("expr".equals(attr)) {
342: String v = String.valueOf(Pnuts.eval(value, context));
343: output.write(PRINT);
344: output.write('"');
345: encode(output, v.toCharArray());
346: output.write(EOL);
347: } else {
348: throw new RuntimeException("unsupported include type");
349: }
350: } else if ("escape".equals(s1)) { /* <%@escape%> */
351: escape[0] = true;
352: } else if ("no-escape".equals(s1)) { /* <%@no-escape%> */
353: escape[0] = false;
354: } else {
355: throw new RuntimeException("not supported directive");
356: }
357: }
358:
359: static void encode(Writer output, char[] buf) throws IOException {
360: encode(output, buf, 0, buf.length);
361: }
362:
363: static void encode(Writer output, char[] src, int offset, int size)
364: throws IOException {
365: char[] buf = new char[512];
366: int bufsize = buf.length;
367: int pos = 0;
368:
369: for (int i = offset; i < offset + size; i++) {
370: if (pos + 2 > bufsize) {
371: output.write(buf, 0, pos);
372: pos = 0;
373: }
374: int c = src[i];
375: switch (c) {
376: case '\\':
377: buf[pos++] = '\\';
378: buf[pos++] = '\\';
379: break;
380: case '"':
381: buf[pos++] = '\\';
382: buf[pos++] = '"';
383: break;
384: default:
385: buf[pos++] = (char) c;
386: }
387: }
388: if (pos > 0) {
389: output.write(buf, 0, pos);
390: }
391: }
392:
393: private Set scriptURLs;
394: private boolean checkUpdates;
395:
396: public DynamicPage(URL scriptURL, String encoding, Context context)
397: throws IOException, ParseException {
398: this (scriptURL, encoding, context, true);
399: }
400:
401: public DynamicPage(URL scriptURL, String encoding, Context context,
402: boolean checkUpdates) throws IOException, ParseException {
403: super (scriptURL, encoding, context);
404: this .checkUpdates = checkUpdates;
405: }
406:
407: protected boolean needToUpdate() {
408: if (scriptURLs == null) {
409: return true;
410: }
411: if (checkUpdates) {
412: try {
413: for (Iterator it = scriptURLs.iterator(); it.hasNext();) {
414: URL url = (URL) it.next();
415: long mod = url.openConnection().getLastModified();
416: if (mod > parsedTime) {
417: return true;
418: }
419: }
420: } catch (IOException e) {
421: // ignore
422: e.printStackTrace();
423: }
424: }
425: return false;
426: }
427:
428: private Reader getReader(InputStream in, Context context)
429: throws UnsupportedEncodingException {
430: if (encoding != null) {
431: return new InputStreamReader(in, encoding);
432: } else {
433: return Runtime.getScriptReader(in, context);
434: }
435: }
436:
437: static URL getDynamicPageURL(URL url, String encoding,
438: Context context, Set scriptURLs) {
439: try {
440: String specURL;
441: if (encoding == null) {
442: specURL = "pea:" + url.toExternalForm();
443: } else {
444: specURL = "pea:" + url.toExternalForm() + "!charset="
445: + encoding;
446: }
447: return new URL(null, specURL, new Handler(context,
448: scriptURLs));
449: } catch (MalformedURLException mue) {
450: mue.printStackTrace();
451: return null;
452: }
453: }
454:
455: protected Compiler getCompiler() {
456: return new Compiler(null, false);
457: }
458:
459: protected Pnuts compile(Pnuts parsed, Context context) {
460: Compiler compiler = getCompiler();
461: if (compiler != null) {
462: return (Pnuts) compiler.compile(parsed, context);
463: } else {
464: return parsed;
465: }
466: }
467:
468: protected void update(Context context) throws IOException,
469: ParseException {
470: try {
471: Set scriptURLs = new HashSet();
472: scriptURLs.add(scriptURL);
473: URL convertedURL = getDynamicPageURL(scriptURL, encoding,
474: context, scriptURLs);
475: InputStream in = null;
476: try {
477: in = convertedURL.openStream();
478: StringWriter sw = new StringWriter();
479: Pnuts parsed = Pnuts.parse(getReader(in, context),
480: convertedURL, context);
481: this .script = compile(parsed, context);
482: this .scriptURLs = scriptURLs;
483: this .parsedTime = System.currentTimeMillis();
484: } finally {
485: if (in != null) {
486: in.close();
487: }
488: }
489: } catch (IOException e) {
490: throw new PnutsException(e, context);
491: }
492: }
493: }
|