001: // PageCompileFrame.java
002: // $Id: PageCompileFrame.java,v 1.16 2000/08/16 21:37:43 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1998.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.jigsaw.pagecompile;
007:
008: import java.io.BufferedOutputStream;
009: import java.io.ByteArrayOutputStream;
010: import java.io.File;
011: import java.io.FileInputStream;
012: import java.io.FileOutputStream;
013: import java.io.FilterOutputStream;
014: import java.io.IOException;
015: import java.io.InputStream;
016: import java.io.OutputStream;
017:
018: import java.util.Hashtable;
019: import java.util.Vector;
020:
021: import org.w3c.jigsaw.frames.HTTPFrame;
022:
023: import org.w3c.jigsaw.http.HTTPException;
024: import org.w3c.jigsaw.http.Reply;
025: import org.w3c.jigsaw.http.Request;
026: import org.w3c.jigsaw.http.httpd;
027:
028: import org.w3c.www.http.HTTP;
029: import org.w3c.www.http.HttpEntityMessage;
030:
031: import org.w3c.www.mime.MimeType;
032:
033: import org.w3c.tools.resources.FileResource;
034: import org.w3c.tools.resources.FramedResource;
035: import org.w3c.tools.resources.ProtocolException;
036: import org.w3c.tools.resources.ReplyInterface;
037: import org.w3c.tools.resources.RequestInterface;
038: import org.w3c.tools.resources.Resource;
039: import org.w3c.tools.resources.ResourceException;
040: import org.w3c.tools.resources.ResourceFrame;
041: import org.w3c.tools.resources.ServerInterface;
042:
043: import org.w3c.util.ObservableProperties;
044:
045: /**
046: * @version $Revision: 1.16 $
047: * @author Benoît Mahé (bmahe@w3.org)
048: */
049: public class PageCompileFrame extends HTTPFrame {
050: /**
051: * debug flag
052: */
053: private static final boolean debug = true;
054:
055: /**
056: * The special interface of Generated Frames.
057: */
058: static Class generatedClass = null;
059:
060: /** Will hold the increments for finding "<java>" */
061: private static byte startIncrements[] = new byte[128];
062:
063: /** Will hold the increments for finding "</java>" */
064: private static byte endIncrements[] = new byte[128];
065:
066: /** The start pattern */
067: private static byte startPat[] = { (byte) '<', (byte) 'j',
068: (byte) 'a', (byte) 'v', (byte) 'a' };
069:
070: /** The end pattern */
071: private static byte endPat[] = { (byte) '<', (byte) '/',
072: (byte) 'j', (byte) 'a', (byte) 'v', (byte) 'a', (byte) '>' };
073:
074: private byte[] unparsed = null;
075:
076: protected static GeneratedClassLoader classloader = null;
077:
078: protected static GeneratedClassLoader getClassLoader() {
079: return classloader;
080: }
081:
082: protected static void createClassLoader(File dir) {
083: if (classloader == null) {
084: classloader = new GeneratedClassLoader(dir);
085: }
086: }
087:
088: protected static GeneratedClassLoader getNewClassLoader() {
089: classloader = new GeneratedClassLoader(classloader);
090: return classloader;
091: }
092:
093: static {
094: try {
095: generatedClass = Class
096: .forName("org.w3c.jigsaw.pagecompile.GeneratedFrame");
097: } catch (Exception ex) {
098: throw new RuntimeException("No GeneratedFrame class found.");
099: }
100: for (int i = 0; i < 128; i++) {
101: startIncrements[i] = 5;
102: endIncrements[i] = 7;
103: }
104: startIncrements[(int) ('<')] = 4;
105: startIncrements[(int) ('j')] = 3;
106: startIncrements[(int) ('a')] = 2;
107: startIncrements[(int) ('v')] = 1;
108:
109: endIncrements[(int) ('<')] = 6;
110: endIncrements[(int) ('/')] = 5;
111: endIncrements[(int) ('j')] = 4;
112: endIncrements[(int) ('a')] = 1;
113: endIncrements[(int) ('v')] = 2;
114: }
115:
116: protected Segment classSegs[] = null;
117: protected Segment importSegs[] = null;
118: protected Segment extendsSegs[] = null;
119: protected Segment implements Segs[] = null;
120: protected Segment bodySegs[] = null;
121:
122: private PageCompileProp props = null;
123:
124: protected PageCompileProp getPageCompileProps() {
125: if (props == null) {
126: httpd server = (httpd) getServer();
127: props = (PageCompileProp) server
128: .getPropertySet(PageCompileProp.PAGE_COMPILE_PROP_NAME);
129: }
130: return props;
131: }
132:
133: private String gcname = null;
134:
135: protected String getGeneratedClassName() {
136: updateGeneratedClassAttributes();
137: return gcname;
138: }
139:
140: private String packagename = null;
141:
142: protected String getGeneratedPackageName() {
143: updateGeneratedClassAttributes();
144: return packagename;
145: }
146:
147: private String packagedClassName = null;
148:
149: protected String getPackagedClassName() {
150: updateGeneratedClassAttributes();
151: if (packagename == null)
152: return gcname;
153: else
154: return packagename + "." + gcname;
155: }
156:
157: private File gcfile = null;
158:
159: protected File getGeneratedClassFile() {
160: updateGeneratedClassAttributes();
161: return gcfile;
162: }
163:
164: private File ccfile = null;
165:
166: private File getCompiledClassFile() {
167: updateGeneratedClassAttributes();
168: return ccfile;
169: }
170:
171: /**
172: * update classfile, package name, classname
173: */
174: private void updateGeneratedClassAttributes() {
175: if ((gcname == null) || (gcfile == null)) {
176: File dir = getPageCompileProps().getCompiledPageDirectory();
177: //dir: /Jigsaw/CompiledPage/
178: String url = fresource.getURLPath();
179: // url: /toto/tata/tutu.html
180: int idx = url.lastIndexOf('.');
181: if (idx != -1)
182: url = url.substring(0, idx);
183: //url: /toto/tata/tutu
184: int idx2 = url.lastIndexOf('/');
185: if (idx2 != -1) {
186: gcname = url.substring(idx2 + 1);
187: //gcname: tutu
188: File gcdir = null;
189: if (idx2 != 0) {
190: String rep = url.substring(0, idx2);
191: //rep: /toto/tata
192: packagename = rep.substring(1);
193: //packagename: toto/tata
194: packagename = packagename.replace('/', '.');
195: //packagename: toto.tata
196: gcdir = new File(dir, rep.substring(1));
197: } else {
198: gcdir = dir;
199: packagename = null;
200: }
201: //gcdir: /Jigsaw/CompiledPage/toto/tata
202: if (!gcdir.exists())
203: gcdir.mkdirs();
204: gcfile = new File(gcdir, gcname + ".java");
205: ccfile = new File(gcdir, gcname + ".class");
206: //gcfile: /Jigsaw/CompiledPage/toto/tata/tutu.java
207: } else {
208: throw new RuntimeException(
209: "Can't update generated class "
210: + "attributes from url : "
211: + fresource.getURLPath());
212: }
213: }
214: }
215:
216: protected boolean classCompiled() {
217: return getCompiledClassFile().exists();
218: }
219:
220: private PageCompiler compiler = null;
221:
222: protected PageCompiler getCompiler() {
223: try {
224: String cname = getPageCompileProps().getCompilerClassName();
225: Class compilerClass = Class.forName(cname);
226: return (PageCompiler) compilerClass.newInstance();
227: } catch (Exception ex) {
228: return null;
229: }
230: }
231:
232: /**
233: * Register the resource and add Properties in httpd.
234: * @param resource The resource to register.
235: */
236: public void registerResource(FramedResource resource) {
237: super .registerResource(resource);
238: if (getPageCompileProps() == null) {
239: synchronized (this .getClass()) {
240: httpd s = (httpd) getServer();
241: if (s != null) {
242: // Register the property sheet if not done yet:
243: ObservableProperties props = s.getProperties();
244: s.registerPropertySet(new PageCompileProp(s));
245: }
246: }
247: }
248: File generatedClassDir = getPageCompileProps()
249: .getCompiledPageDirectory();
250: createClassLoader(generatedClassDir);
251: }
252:
253: protected byte[] readUnparsed() throws IOException {
254: File file = fresource.getFile();
255: ByteArrayOutputStream out = new ByteArrayOutputStream(
256: (int) file.length());
257:
258: FileInputStream in = new FileInputStream(file);
259:
260: byte[] buf = new byte[4096];
261: int len = 0;
262:
263: while ((len = in.read(buf)) != -1)
264: out.write(buf, 0, len);
265:
266: in.close();
267: out.close();
268:
269: unparsed = out.toByteArray();
270: return unparsed;
271: }
272:
273: /**
274: * Analogous to standard C's <code>strncmp</code>, for byte arrays.
275: * (Should be in some utility package, I'll put it here for now)
276: * @param ba1 the first byte array
277: * @param off1 where to start in the first array
278: * @param ba2 the second byte array
279: * @param off2 where to start in the second array
280: * @param n the length to compare up to
281: * @return <strong>true</strong> if both specified parts of the arrays are
282: * equal, <strong>false</strong> if they aren't .
283: */
284: public static final boolean byteArrayNEquals(byte[] ba1, int off1,
285: byte[] ba2, int off2, int n) {
286: // So that only one addition is needed inside loop
287: int corr = off2 - off1;
288: int max = n + off1;
289: for (int i = off1; i < max; i++)
290: if (ba1[i] != ba2[i + corr])
291: return false;
292: return true;
293: }
294:
295: /**
296: * Does the same as Character.isSpace, without need to cast the
297: * byte into a char.
298: * @param ch the character
299: * @return whether or not ch is ASCII white space
300: * @see java.lang.Character#isSpace
301: */
302: private final boolean isSpace(byte ch) {
303: return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
304: }
305:
306: /**
307: * isSpace or '=';
308: * @param ch the character
309: * @return whether or not ch is ASCII white space or '='
310: */
311: private final boolean isSpaceOrEqual(byte ch) {
312: return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
313: || ch == '=';
314: }
315:
316: protected int parseType(byte unparsed[], int startParam,
317: int endParam) {
318: //parsing "type = value "
319: StringBuffer typebf = new StringBuffer(10);
320: StringBuffer valbf = new StringBuffer(10);
321: String value = null;
322: int typeidx = startParam;
323: char ch;
324:
325: //skip spaces
326: while ((typeidx <= endParam) && (isSpace(unparsed[typeidx])))
327: typeidx++;
328: //type
329: while ((typeidx <= endParam)
330: && (!isSpaceOrEqual(unparsed[typeidx]))) {
331: ch = (char) unparsed[typeidx++];
332: typebf.append(ch);
333: }
334: if (!(typebf.toString().equalsIgnoreCase("type")))
335: return Segment.getDefaultType();
336: //skip space and '=';
337: while ((typeidx <= endParam)
338: && (isSpaceOrEqual(unparsed[typeidx])))
339: typeidx++;
340: //value
341: while ((typeidx <= endParam) && (!isSpace(unparsed[typeidx]))) {
342: ch = (char) unparsed[typeidx++];
343: valbf.append(ch);
344: }
345: return Segment.getType(valbf.toString());
346: }
347:
348: protected Segment[] parse() {
349: unparsed = null;
350: try {
351: unparsed = readUnparsed();
352: } catch (IOException ex) {
353: return null;
354: }
355:
356: Vector buildSegments = new Vector(20);
357: int byteIdx = 0;
358: int StartSeg = 0;
359: int startParam = 0;
360: int type = -1;
361: byte ch;
362:
363: do {
364: byteIdx += 4;
365: while (byteIdx < unparsed.length) {
366: if ((ch = unparsed[byteIdx]) == (byte) 'a') {
367: if (byteArrayNEquals(unparsed, byteIdx - 4,
368: startPat, 0, 4)) {
369: break;
370: }
371: }
372: // This is an ugly work-around to the
373: // absence of unsigned bytes in Java.
374: byteIdx += startIncrements[ch >= 0 ? ch : 0];
375: }
376:
377: if (++byteIdx >= unparsed.length)
378: break; // Nothing found
379:
380: //we just found a <java tag
381: if (byteIdx > 6)
382: buildSegments.addElement(new Segment(StartSeg,
383: byteIdx - 6));
384:
385: startParam = byteIdx;
386: //looking for '>'
387: while (byteIdx < unparsed.length) {
388: if (unparsed[byteIdx] == (byte) '>')
389: break;
390: byteIdx++;
391: }
392:
393: if (++byteIdx >= unparsed.length)
394: break; // Nothing found
395:
396: //parse param now...
397: type = parseType(unparsed, startParam, byteIdx - 2);
398:
399: //looking for </java>
400: StartSeg = byteIdx;
401: byteIdx += 6;
402: while (byteIdx < unparsed.length) {
403: if ((ch = unparsed[byteIdx]) == (byte) '>') {
404: if (byteArrayNEquals(unparsed, byteIdx - 6, endPat,
405: 0, 6)) {
406: break;
407: }
408: }
409: // This is an ugly work-around to the absence of
410: // unsigned bytes in Java:
411: byteIdx += endIncrements[ch >= 0 ? ch : 0];
412: }
413:
414: if (++byteIdx >= unparsed.length)
415: break; // No end found
416:
417: //we just found a </java> tag
418: buildSegments.addElement(new Segment(StartSeg, byteIdx - 8,
419: type));
420: StartSeg = byteIdx;
421: } while (byteIdx < unparsed.length);
422:
423: // Add the last chunk of unparsed text as a segment
424: buildSegments
425: .addElement(new Segment(StartSeg, unparsed.length));
426:
427: Segment[] segs = new Segment[buildSegments.size()];
428: buildSegments.copyInto(segs);
429: return segs;
430:
431: }
432:
433: private Segment[] getSegmentArrayFromVector(Vector V) {
434: int size = V.size();
435: if (size < 1)
436: return null;
437: Segment[] segs = new Segment[size];
438: V.copyInto(segs);
439: return segs;
440: }
441:
442: protected void separateSegments(Segment segments[]) {
443: Vector classV = new Vector(3);
444: Vector importV = new Vector(3);
445: Vector extendsV = new Vector(1);
446: Vector implements V = new Vector(1);
447: Vector bodyV = new Vector(5);
448:
449: for (int i = 0; i < segments.length; i++) {
450: switch (segments[i].getType()) {
451: case Segment.CLASS:
452: classV.addElement(segments[i]);
453: break;
454: case Segment.IMPORT:
455: importV.addElement(segments[i]);
456: break;
457: case Segment.EXTENDS:
458: extendsV.addElement(segments[i]);
459: break;
460: case Segment.IMPLEMENTS:
461: implements V.addElement(segments[i]);
462: break;
463: case Segment.CODE:
464: case Segment.PRINT:
465: case Segment.TEXT:
466: default:
467: bodyV.addElement(segments[i]);
468: }
469: }
470: classSegs = getSegmentArrayFromVector(classV);
471: importSegs = getSegmentArrayFromVector(importV);
472: extendsSegs = getSegmentArrayFromVector(extendsV);
473: implements Segs = getSegmentArrayFromVector(implements V);
474: bodySegs = getSegmentArrayFromVector(bodyV);
475: }
476:
477: protected byte[] getPackageStatement() {
478: if (getGeneratedPackageName() != null)
479: return (new String("package " + getGeneratedPackageName()
480: + ";\n\n")).getBytes();
481: return null;
482: }
483:
484: protected byte[] getClassDeclarationStatement() {
485: return (new String("public class " + getGeneratedClassName()
486: + " extends ")).getBytes();
487: }
488:
489: protected String getFilePath() {
490: String path = fresource.getFile().getAbsolutePath();
491: StringBuffer filepath = new StringBuffer();
492: int idx = path.indexOf('\\');
493: if (idx == -1)
494: return path;
495: while ((idx = path.indexOf('\\')) != -1) {
496: filepath.append(path.substring(0, idx));
497: filepath.append("\\\\");
498: path = path.substring(idx + 1);
499: }
500: filepath.append(path);
501: return filepath.toString();
502: }
503:
504: protected byte[] getGetMethodDeclaration() {
505: StringBuffer decl = new StringBuffer(256);
506: decl
507: .append(" protected void get(org.w3c.jigsaw.http.Request "
508: + "request,\n");
509: decl.append(" org.w3c.jigsaw.http.Reply "
510: + "reply,\n");
511: decl.append(" "
512: + "org.w3c.jigsaw.pagecompile.PageCompileOutputStream "
513: + "out)\n");
514: decl.append(" throws java.io.IOException\n");
515: decl.append(" {\n");
516: decl
517: .append(" org.w3c.jigsaw.pagecompile.PageCompileFile "
518: + "_file = "
519: + "new org.w3c.jigsaw.pagecompile.PageCompileFile(\"");
520: decl.append(getFilePath() + "\");\n");
521: return decl.toString().getBytes();
522: }
523:
524: protected byte[] getClassEnd() {
525: byte end[] = { (byte) ' ', (byte) ' ', (byte) ' ', (byte) ' ',
526: (byte) '}', (byte) '\n', (byte) '}' };
527: return end;
528: }
529:
530: protected byte[] getSegmentBytes(Segment seg) {
531: if (seg.getType() == Segment.PRINT) {
532: byte part1[] = (new String(
533: " out.print(String.valueOf(")).getBytes();
534: byte part3[] = { (byte) ')', (byte) ')', (byte) ';',
535: (byte) '\n' };
536: int seglen = seg.end - seg.start + 1;
537: int len = part1.length + seglen + part3.length;
538: byte statement[] = new byte[len];
539: System.arraycopy(part1, 0, statement, 0, part1.length);
540: System.arraycopy(unparsed, seg.start, statement,
541: part1.length, seglen);
542: System.arraycopy(part3, 0, statement,
543: part1.length + seglen, part3.length);
544: return statement;
545: } else if (seg.getType() != Segment.TEXT) {
546: int len = seg.end - seg.start + 1;
547: byte segbytes[] = new byte[len];
548: System.arraycopy(unparsed, seg.start, segbytes, 0, len);
549: return segbytes;
550: } else {
551: return (new String(" _file.writeBytes(" + seg.start
552: + "," + seg.end + ",out);\n")).getBytes();
553: }
554: }
555:
556: /**
557: * Generate the frame.
558: * @param request the incomming request.
559: * @exception ResourceException if a fatal error occurs.
560: * @exception ProtocolException if compilation failed
561: */
562: public GeneratedFrame generateFrame(Request request)
563: throws ResourceException, ProtocolException {
564: //big stuff is here!
565: separateSegments(parse());
566: //create the class file
567: File classFile = getGeneratedClassFile();
568: if (classFile.exists())
569: classFile.delete();
570: if (getCompiledClassFile().exists())
571: getCompiledClassFile().delete();
572: try {
573: BufferedOutputStream out = new BufferedOutputStream(
574: new FileOutputStream(classFile));
575: //package?
576: byte[] pack = getPackageStatement();
577: if (pack != null)
578: out.write(pack);
579: //import what?
580: if (importSegs != null) {
581: for (int i = 0; i < importSegs.length; i++)
582: out.write(getSegmentBytes(importSegs[i]));
583: out.write('\n');
584: }
585: //class delcaration
586: out.write(getClassDeclarationStatement());
587: //extends what?
588: if (extendsSegs != null) {
589: //only one extends!
590: if (extendsSegs.length > 0)
591: out.write(getSegmentBytes(extendsSegs[0]));
592: out.write(' ');
593: } else {
594: out.write((new String(
595: "org.w3c.jigsaw.pagecompile.GeneratedFrame "))
596: .getBytes());
597: }
598: //implements what?
599: if (implements Segs != null) {
600: //only one implements tag!
601: if (implements Segs.length > 0) {
602: byte imp[] = { (byte) 'i', (byte) 'm', (byte) 'p',
603: (byte) 'l', (byte) 'e', (byte) 'm',
604: (byte) 'e', (byte) 'n', (byte) 't',
605: (byte) 's', (byte) ' ' };
606: out.write(imp);
607: out.write(getSegmentBytes(implements Segs[0]));
608: }
609: }
610: out.write(' ');
611: out.write('{');
612: out.write('\n');
613: out.write('\n');
614: //class segments
615: if (classSegs != null) {
616: for (int i = 0; i < classSegs.length; i++)
617: out.write(getSegmentBytes(classSegs[i]));
618: out.write('\n');
619: }
620: //method decl
621: out.write(getGetMethodDeclaration());
622: //body now
623: if (bodySegs != null)
624: for (int i = 0; i < bodySegs.length; i++)
625: out.write(getSegmentBytes(bodySegs[i]));
626: out.write(getClassEnd());
627: out.flush();
628: out.close();
629: } catch (IOException ex) {
630:
631: }
632: PageCompiler compiler = getCompiler();
633: if (compiler == null)
634: throw new ResourceException("Can't load compiler: "
635: + getPageCompileProps().getCompilerClassName());
636: String args[] = { classFile.getAbsolutePath() };
637: PageCompileOutputStream out = new PageCompileOutputStream();
638: if (!compiler.compile(args, out)) {
639: //FIXME Warning
640: if (out.size() > 0) {
641: Reply error = request
642: .makeReply(HTTP.INTERNAL_SERVER_ERROR);
643: error.setStream(out.getInputStream());
644: error.setContentLength(out.size());
645: error.setContentType(MimeType.TEXT_PLAIN);
646: throw new HTTPException(error);
647: }
648: }
649: //load class now
650: try {
651: GeneratedClassLoader loader = getClassLoader();
652: if (loader.classChanged(getPackagedClassName()))
653: loader = getNewClassLoader();
654: Class generatedClass = loader
655: .loadClass(getPackagedClassName());
656: GeneratedFrame gframe = (GeneratedFrame) generatedClass
657: .newInstance();
658: return gframe;
659: } catch (Exception ex) {
660: throw new ResourceException(ex.getMessage());
661: }
662: }
663:
664: protected void registerNewGeneratedFrame(Request request)
665: throws ResourceException, ProtocolException {
666: //remove the old one if any
667: ResourceFrame frames[] = collectFrames(generatedClass);
668: if (frames != null) {
669: for (int i = 0; i < frames.length; i++)
670: unregisterFrame(frames[i]);
671: }
672: //add the new one
673: GeneratedFrame frame = null;
674: frame = generateFrame(request);
675: if (frame != null) {
676: Hashtable defs = new Hashtable(3);
677: defs.put("identifier", "generated-frame");
678: defs.put("content-type", MimeType.TEXT_HTML);
679: registerFrame(frame, defs);
680: }
681: }
682:
683: protected void checkContent(Request request)
684: throws ResourceException, ProtocolException {
685: if (fresource != null) {
686: File file = fresource.getFile();
687: long lmt = file.lastModified();
688: long cmt = fresource.getFileStamp();
689: if ((!classCompiled()) || ((cmt < 0) || (cmt < lmt))) {
690: fresource.updateFileAttributes();
691: registerNewGeneratedFrame(request);
692: }
693: }
694: }
695:
696: /**
697: * Makes sure that checkContent() is called on _any_ HTTP method,
698: * so that the internal representation of commands is always consistent.
699: * @param request The HTTPRequest
700: * @return a ReplyInterface instance
701: * @exception ProtocolException If processing the request failed.
702: * @exception ResourceException If this resource got a fatal error.
703: */
704: public ReplyInterface perform(RequestInterface request)
705: throws ProtocolException, ResourceException {
706: if (!checkRequest(request))
707: return null;
708: checkContent((Request) request);
709: return super.perform(request);
710: }
711:
712: }
|