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 }