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 006package fc.util.pagetemplate; 007 008import java.util.*; 009import java.io.*; 010 011import fc.io.*; 012import fc.util.*; 013 014/** 015A classloader that loads templates. Each page is loaded by it's own 016separate ClassLoader. 017<p> 018If a page changes on disk (this is tracked by {@link TemplateMgr}), then the 019new page is loaded by a new instance of this class. 020@author hursh jain 021*/ 022public class TemplateClassLoader extends ClassLoader 023{ 024/* 025We don't need to use definePackage for our classloaders, 026there are no manifest based versions associated with pages. 027 028Say a java file in package foo.bar exists in: 029 /mydir/foo/bar/baz.java 030 031Then running "javac -classpath /mydir foo.bar.baz.java" will 032result in: 033 /mydir/foo/bar/baz.class 034 035Now say: 036 cd /mydir/foo/bar/ 037 java -classpath "." foo.bar.baz 038 039This will cause an error since "java" actually checks to 040see that the classpath it _used_ is the same as the classpath 041in the file it found. (in this example, java used "." but found 042foo.bar.baz.class). 043 044However, we can read a byte[] corresponding to a .class file 045from anywhere on the disk, network, database and this has 046(typically for custom loaders) nothing to do with the classpath. 047 048Therefore, when implenting a custom classloader, we can 049maintain a directory structure for generated class files OR 050put all generated class files in one directory, as say 051/mydir/foo_bar_baz.class etc OR do both. 052 053GENERATED TEMPLATE CLASSES ARE ALL IN THE SAME PACKAGE (molly.pagetemplate) AND 054ARE ORGANIZED IN A DIRECTORY STRUCTURE THAT MIRRORS THE SOURCE 055HEIRARCHY. THIS MAKES GENERATED .java PAGES (CORRESPONDING TO THE SOURCE 056.mp) EASIER TO FIND FOR HUMANS. 057 058Instead of simply implementing findClass, we _could_ also 059override loadClass(). This allows us to warn/error out if 060the parent classloader was able to load the page's class 061file (which should never happen since the classpath containing 062the template .class files should NOT be in the system classpath). 063If it IS in the system classpath, page re-loading will not 064work, which is an error for our purposes. 065 066We still need to delegate to a parent classloader for non-template 067classes referenced from a page. To simplify things, I've used 068the delegation model here and will give the parent classloader 069first crack at loading everything....this is the way it should 070always be anyway. 071*/ 072 073private static final boolean dbg = false; 074Log log; 075 076/** 077@param scratch absolute path to the scratch dir 078@param log logging destination. 079*/ 080public TemplateClassLoader(Log log) 081 { 082 this.log = log; 083 } 084 085/** 086Calls {@link TemplateClassLoader} with the logger set to: 087<tt>fc.web.page.TemplateClassLoader</tt> 088*/ 089public TemplateClassLoader() 090 { 091 this.log = Log.getDefault(); 092 } 093 094Map class_file_paths = new HashMap(); 095 096/** 097Loads a class corresponding to a page (i.e., a name that starts 098with {@link Page.PackageName} from. Delegates the loading of all 099other classes to the parent classloader (typically the system 100classloader). 101<p> 102If 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*/ 110public 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 175Class 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 215public 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}