001 // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org)
002 // The Molly framework is freely distributable under the terms of an
003 // MIT-style license. For details, see the molly pages web site at:
004 // http://www.mollypages.org/. Use, modify, have fun !
005
006 package fc.util.pagetemplate;
007
008 import java.util.*;
009 import java.io.*;
010
011 import fc.io.*;
012 import fc.util.*;
013
014 /**
015 A classloader that loads templates. Each page is loaded by it's own
016 separate ClassLoader.
017 <p>
018 If a page changes on disk (this is tracked by {@link TemplateMgr}), then the
019 new page is loaded by a new instance of this class.
020 @author hursh jain
021 */
022 public class TemplateClassLoader extends ClassLoader
023 {
024 /*
025 We don't need to use definePackage for our classloaders,
026 there are no manifest based versions associated with pages.
027
028 Say a java file in package foo.bar exists in:
029 /mydir/foo/bar/baz.java
030
031 Then running "javac -classpath /mydir foo.bar.baz.java" will
032 result in:
033 /mydir/foo/bar/baz.class
034
035 Now say:
036 cd /mydir/foo/bar/
037 java -classpath "." foo.bar.baz
038
039 This will cause an error since "java" actually checks to
040 see that the classpath it _used_ is the same as the classpath
041 in the file it found. (in this example, java used "." but found
042 foo.bar.baz.class).
043
044 However, we can read a byte[] corresponding to a .class file
045 from anywhere on the disk, network, database and this has
046 (typically for custom loaders) nothing to do with the classpath.
047
048 Therefore, when implenting a custom classloader, we can
049 maintain a directory structure for generated class files OR
050 put all generated class files in one directory, as say
051 /mydir/foo_bar_baz.class etc OR do both.
052
053 GENERATED TEMPLATE CLASSES ARE ALL IN THE SAME PACKAGE (molly.pagetemplate) AND
054 ARE ORGANIZED IN A DIRECTORY STRUCTURE THAT MIRRORS THE SOURCE
055 HEIRARCHY. THIS MAKES GENERATED .java PAGES (CORRESPONDING TO THE SOURCE
056 .mp) EASIER TO FIND FOR HUMANS.
057
058 Instead of simply implementing findClass, we _could_ also
059 override loadClass(). This allows us to warn/error out if
060 the parent classloader was able to load the page's class
061 file (which should never happen since the classpath containing
062 the template .class files should NOT be in the system classpath).
063 If it IS in the system classpath, page re-loading will not
064 work, which is an error for our purposes.
065
066 We still need to delegate to a parent classloader for non-template
067 classes referenced from a page. To simplify things, I've used
068 the delegation model here and will give the parent classloader
069 first crack at loading everything....this is the way it should
070 always be anyway.
071 */
072
073 private static final boolean dbg = false;
074 Log log;
075
076 /**
077 @param scratch absolute path to the scratch dir
078 @param log logging destination.
079 */
080 public TemplateClassLoader(Log log)
081 {
082 this.log = log;
083 }
084
085 /**
086 Calls {@link TemplateClassLoader} with the logger set to:
087 <tt>fc.web.page.TemplateClassLoader</tt>
088 */
089 public TemplateClassLoader()
090 {
091 this.log = Log.getDefault();
092 }
093
094 Map class_file_paths = new HashMap();
095
096 /**
097 Loads a class corresponding to a page (i.e., a name that starts
098 with {@link Page.PackageName} from. Delegates the loading of all
099 other classes to the parent classloader (typically the system
100 classloader).
101 <p>
102 If the page suffix is <tt>.mp</tt>, then a name such as
103 <tt>foo/bar/baz.page</tt> is loaded from the scratch dir (from
104 <tt><scratchdir>/foo/bar/baz.class</tt>).
105
106 @param name a relative page path-name (relative to the page
107 root directory), for example <tt>foo/bar/my.page</tt>
108 or <tt>./my.page</tt>
109 */
110 public Class loadClass(String name, boolean resolve) throws ClassNotFoundException
111 {
112 if (dbg) log.bug("enter loadClass(): name=", name, " resolve=", resolve);
113
114 //we could implement findClass() instead of loadClass
115 if (name.startsWith(TemplatePage.PACKAGE_NAME))
116 {
117 Class c = findLoadedClass(name);
118 if (c != null)
119 return c ;
120
121 if (dbg) log.bug("Name starts with:[", TemplatePage.PACKAGE_NAME, "], loading page: ", name);
122 final int pkgnamelen = TemplatePage.PACKAGE_NAME.length() + 1; //"foo.pkg."
123
124 name = name.substring(pkgnamelen, name.length());
125
126 if (dbg) System.out.println("[A] name=" + name);
127 //for inner, anonymous classes
128 //1) pagemgr first says: load "molly.page.foo.class"
129 //2) foo.mp contains a anonymous inner class foo.mp$1
130 //3) this method is called again with "molly.page.foo.mp$1"
131 // as the argument
132 //4) we need to load "foo.mp$1.class"
133
134 if (! name.endsWith(".class"))
135 {
136 if (dbg) System.out.println("[B]");
137 String outer_class_name = name.substring(0, name.indexOf("$"));
138 if (dbg) System.out.println("[C] outer=" + outer_class_name);
139 String path = (String) class_file_paths.get(outer_class_name);
140 if (dbg) System.out.println("[D] path=" + path);
141 if (path == null) {
142 throw new ClassNotFoundException("Internal error: cannot load anonymous/inner classe: " + name);
143 }
144
145 name = path + name + ".class";
146 if (dbg) log.warn("anonname=",name);
147 }
148 else{
149 if (dbg) System.out.println("[E]");
150 String dirname = StringUtil.dirName(name); //has ending '/'
151 String filename = StringUtil.fileName(name);
152 filename = filename.substring(0, filename.indexOf(".class"));
153 class_file_paths.put(filename, dirname);
154 if (dbg) log.bug("name=",name);
155 if (dbg) log.bug("dirname=",dirname);
156 }
157
158 c = readClassFromDisk(new File(name));
159
160 if (resolve) {
161 resolveClass(c);
162 }
163 return c;
164 }
165
166 if (dbg) log.bug("Delegating to thread-context classloader to load class: ", name);
167 //we cannot use the parent classloader because it will not have
168 //classes inside of WEB-INF (the parent classloader is a jvm level
169 //classloader). We need to use the servlet containers classloader
170 //instead.
171 //return super.loadClass(name, resolve);
172 return Thread.currentThread().getContextClassLoader().loadClass(name);
173 }
174
175 Class readClassFromDisk(File classfile) throws ClassNotFoundException
176 {
177 Argcheck.istrue(classfile.exists(), "The specified file does not exist: " + classfile.getAbsolutePath());
178
179 byte[] buf = null;
180
181 try {
182 if (dbg) log.bug("Using filename:", classfile);
183
184 buf = IOUtil.fileToByteArray(classfile);
185
186 if (buf == null) {
187 throw new ClassNotFoundException(classfile.getPath());
188 }
189
190 }
191 catch (Exception e) {
192 throw new ClassNotFoundException(classfile.getPath());
193 }
194
195 /*
196 definePackage(Page.PACKAGE_NAME, null, null, null, null, null, null, null);
197
198 this works ONCE per instance of the classloader -- will crap out
199 if we use the same instance to define more than 1 class. defining
200 a package is not needed at all, but may be nice to have for logging
201 purposes/setting logging levels. If we have an inner class, this
202 method will be invoked more than once (one per inner class which
203 uses this same classloader).
204
205 All of this is very obscure and if this causes problems, remove.
206 */
207 if (getPackage(TemplatePage.PACKAGE_NAME) == null)
208 definePackage(TemplatePage.PACKAGE_NAME, null, null, null, null, null, null, null);
209
210 Class c = defineClass(null, buf, 0, buf.length);
211
212 return c;
213 }
214
215 public static void main (String args[]) throws Exception
216 {
217 Args myargs = new Args(args);
218 myargs.setUsage("java " + ClassUtil.getClassName() +
219 " -file full-path-to-class-file");
220 String page = TemplatePage.PACKAGE_NAME + "." + myargs.getRequired("file");
221
222 TemplateClassLoader cl = new TemplateClassLoader();
223 cl.log.setLevel("debug");
224
225 System.out.println("Loading java.lang.object: ");
226 Class c = cl.loadClass("java.lang.Object");
227 System.out.println(ClassUtil.getClassLoaderInfo(c));
228
229 System.out.println("Loading " + page);
230 c = cl.loadClass(page);
231 System.out.println(ClassUtil.getClassLoaderInfo(c));
232 System.out.println("Package for this class: " + c.getPackage());
233 }
234
235 }