001: /*
002: * PnutsClassLoader.java
003: *
004: * Copyright (c) 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 org.pnuts.lang;
010:
011: import java.io.*;
012: import java.util.*;
013: import java.net.*;
014: import java.security.*;
015: import java.lang.reflect.*;
016: import pnuts.lang.Pnuts;
017: import pnuts.lang.Context;
018: import pnuts.lang.SimpleNode;
019: import pnuts.lang.Runtime;
020: import pnuts.lang.ParseException;
021: import pnuts.lang.PnutsParser;
022: import pnuts.lang.ParseEnvironment;
023: import pnuts.compiler.Compiler;
024: import pnuts.compiler.ClassFile;
025:
026: /**
027: * A ClassLoader to compile and load classes written in Pnuts
028: */
029: public class PnutsClassLoader extends ClassLoader {
030: private final static boolean DEBUG = false;
031: private final static String DEFAULT_PREFIX = "";
032: private final static String DEFAULT_SUFFIX = ".pnc";
033:
034: private String prefix = DEFAULT_PREFIX;
035: private String suffix = DEFAULT_SUFFIX;
036: private String encoding;
037: private Context context;
038: private Map table = new HashMap();
039:
040: /**
041: * Constructor
042: *
043: * @param context the context in which the scripts are executed
044: */
045: public PnutsClassLoader(Context context) {
046: this .context = context;
047: }
048:
049: /**
050: * Constructor
051: *
052: * @param parent the parent class loader
053: * @param context the context in which the scripts are executed
054: */
055: public PnutsClassLoader(ClassLoader parent, Context context) {
056: super (parent);
057: this .context = context;
058: }
059:
060: /**
061: * Get the file name extension
062: *
063: * @return the file name extension (the default=.pnc)
064: */
065: public String getSuffix() {
066: return this .suffix;
067: }
068:
069: /**
070: * Set the file name extension
071: *
072: * @param suffix the file name extension (the default=.pnc)
073: */
074: public void setSuffix(String suffix) {
075: this .suffix = suffix;
076: }
077:
078: /**
079: * Get the prefix
080: *
081: * @return prefix the file name extension
082: */
083: public String getPrefix() {
084: return this .prefix;
085: }
086:
087: /**
088: * Set the prefix
089: *
090: * @param prefix the file name extension (the default=/)
091: */
092: public void setPrefix(String prefix) {
093: this .prefix = prefix;
094: }
095:
096: /**
097: * Get the resource name for the specified class name
098: */
099: protected String getScriptResourceName(String className) {
100: int idx = className.lastIndexOf('.');
101: if (idx < 0) {
102: return prefix + className + suffix;
103: } else {
104: return prefix + className.replace('.', '/') + suffix;
105: }
106: }
107:
108: /**
109: * Get the character encoding of the parser
110: */
111: public String getEncoding() {
112: return this .encoding;
113: }
114:
115: /**
116: * Set the character encoding of the parser
117: *
118: * @param enc the character encoding
119: */
120: public void setEncoding(String enc) {
121: this .encoding = enc;
122: }
123:
124: protected SimpleNode parse(InputStream in, URL url)
125: throws IOException, ParseException {
126: Reader reader;
127: if (encoding != null) {
128: reader = new BufferedReader(new InputStreamReader(in,
129: encoding));
130: } else {
131: reader = new BufferedReader(new InputStreamReader(in));
132: }
133: return parse(reader, url);
134: }
135:
136: /**
137: * Parse the specified script
138: */
139: public static SimpleNode parse(Reader reader, URL url)
140: throws IOException, ParseException {
141: ParseEnvironment env = DefaultParseEnv.getInstance(url);
142: return new PnutsParser(reader).ClassScript(env);
143: }
144:
145: private static void dump(String name, byte[] code) {
146: try {
147: FileOutputStream fout = new FileOutputStream("c:/tmp/"
148: + name + ".class");
149: fout.write(code);
150: fout.close();
151: } catch (IOException e) {
152: e.printStackTrace();
153: }
154: }
155:
156: void attachContext(Class cls) {
157: try {
158: Method m = cls.getMethod("attach",
159: new Class[] { Context.class });
160: m.invoke(null, new Object[] { new Context(context) });
161: } catch (Exception e) {
162: e.printStackTrace();
163: }
164: }
165:
166: /**
167: * Compile a script
168: *
169: * @param className the class name
170: * @param url the location of the script
171: */
172: protected List compile(String className, URL url)
173: throws IOException, ParseException {
174: InputStream in = url.openStream();
175: if (in != null) {
176: try {
177: if (context.isVerbose()) {
178: PrintWriter writer = context.getErrorWriter();
179: writer.println("compiling " + url);
180: }
181: return compile(className, parse(in, url), url);
182: } finally {
183: try {
184: in.close();
185: } catch (IOException e) {
186: /* skip */
187: }
188: }
189: }
190: return null;
191: }
192:
193: /**
194: * Compile a script
195: *
196: * @param className the class name
197: * @param node the syntax tree to be compiled
198: * @param scriptSource the location of the script
199: */
200: protected List compile(String className, SimpleNode node,
201: Object scriptSource) throws IOException {
202: List/*<ClassFile>*/helperClassFiles = new ArrayList();
203: ClassFile classFile = new Compiler(className, false, true)
204: .compileClassScript(node, scriptSource,
205: helperClassFiles);
206:
207: List classes = new ArrayList();
208: for (int i = 0, n = helperClassFiles.size(); i < n; i++) {
209: ByteArrayOutputStream bout = new ByteArrayOutputStream();
210: ClassFile cf = (ClassFile) helperClassFiles.get(i);
211: cf.write(bout);
212: byte[] bytecode = bout.toByteArray();
213: try {
214: Class cls = defineClass(cf.getClassName(), bytecode, 0,
215: bytecode.length);
216: classes.add(cls);
217: } catch (Throwable e) {
218: if (DEBUG) {
219: e.printStackTrace();
220: }
221: return null;
222: }
223: }
224:
225: ByteArrayOutputStream bout = new ByteArrayOutputStream();
226: classFile.write(bout);
227: byte[] bytecode = bout.toByteArray();
228: try {
229: Class cls = defineClass(className, bytecode, 0,
230: bytecode.length);
231: if (context != null
232: && context != Runtime.getThreadContext()) {
233: attachContext(cls);
234: }
235: table.put(className, cls);
236: classes.add(cls);
237: } catch (Throwable e) {
238: // if (DEBUG){
239: e.printStackTrace();
240: // }
241: return null;
242: } finally {
243: if (DEBUG) {
244: dump(className, bytecode);
245: System.out.println("classes are " + classes);
246: }
247: }
248:
249: return classes;
250: }
251:
252: /**
253: * Get the protection domain
254: */
255: protected ProtectionDomain getProtectionDomain() {
256: return null;
257: }
258:
259: /**
260: * Specifies how to handle parse exceptions
261: */
262: protected void handleParseException(ParseException e) {
263: e.printStackTrace();
264: }
265:
266: protected Class findClass(String className)
267: throws ClassNotFoundException {
268: Class cls = (Class) table.get(className);
269: if (cls != null) {
270: return cls;
271: }
272: try {
273: return super .findClass(className);
274: } catch (ClassNotFoundException cfe) {
275: // skip
276: }
277: String resource = getScriptResourceName(className);
278: URL url = getResource(resource);
279: if (url != null) {
280: try {
281: List classes = compile(className, url);
282: if (classes != null && classes.size() > 0) {
283: return (Class) classes.get(classes.size() - 1);
284: }
285: } catch (IOException e1) {
286: return super .findClass(className);
287: } catch (ParseException e2) {
288: handleParseException(e2);
289: return super.findClass(className);
290: }
291: }
292: return super.findClass(className);
293: }
294: }
|