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 log logging destination. 078*/ 079public TemplateClassLoader(Log log) 080 { 081 this.log = log; 082 } 083 084/** 085Calls {@link TemplateClassLoader} with the logger set to: 086<code>fc.web.page.TemplateClassLoader</code> 087*/ 088public TemplateClassLoader() 089 { 090 this.log = Log.getDefault(); 091 } 092 093Map class_file_paths = new HashMap(); 094 095/** 096Loads a class corresponding to a page (i.e., a name that starts 097with <code>Page.PackageName</code> from. Delegates the loading of all 098other classes to the parent classloader (typically the system 099classloader). 100<p> 101If the page suffix is <code>.mp</code>, then a name such as 102<code>foo/bar/baz.page</code> is loaded from the scratch dir (from 103<code><scratchdir>/foo/bar/baz.class</code>). 104 105@param name a relative page path-name (relative to the page 106 root directory), for example <code>foo/bar/my.page</code> 107 or <code>./my.page</code> 108*/ 109public Class loadClass(String name, boolean resolve) throws ClassNotFoundException 110 { 111 if (dbg) log.bug("enter loadClass(): name=", name, " resolve=", resolve); 112 113 //we could implement findClass() instead of loadClass 114 if (name.startsWith(TemplatePage.PACKAGE_NAME)) 115 { 116 Class c = findLoadedClass(name); 117 if (c != null) 118 return c ; 119 120 if (dbg) log.bug("Name starts with:[", TemplatePage.PACKAGE_NAME, "], loading page: ", name); 121 final int pkgnamelen = TemplatePage.PACKAGE_NAME.length() + 1; //"foo.pkg." 122 123 name = name.substring(pkgnamelen, name.length()); 124 125 if (dbg) System.out.println("[A] name=" + name); 126 //for inner, anonymous classes 127 //1) pagemgr first says: load "molly.page.foo.class" 128 //2) foo.mp contains a anonymous inner class foo.mp$1 129 //3) this method is called again with "molly.page.foo.mp$1" 130 // as the argument 131 //4) we need to load "foo.mp$1.class" 132 133 if (! name.endsWith(".class")) 134 { 135 if (dbg) System.out.println("[B]"); 136 String outer_class_name = name.substring(0, name.indexOf("$")); 137 if (dbg) System.out.println("[C] outer=" + outer_class_name); 138 String path = (String) class_file_paths.get(outer_class_name); 139 if (dbg) System.out.println("[D] path=" + path); 140 if (path == null) { 141 throw new ClassNotFoundException("Internal error: cannot load anonymous/inner classe: " + name); 142 } 143 144 name = path + name + ".class"; 145 if (dbg) log.warn("anonname=",name); 146 } 147 else{ 148 if (dbg) System.out.println("[E]"); 149 String dirname = StringUtil.dirName(name); //has ending '/' 150 String filename = StringUtil.fileName(name); 151 filename = filename.substring(0, filename.indexOf(".class")); 152 class_file_paths.put(filename, dirname); 153 if (dbg) log.bug("name=",name); 154 if (dbg) log.bug("dirname=",dirname); 155 } 156 157 c = readClassFromDisk(new File(name)); 158 159 if (resolve) { 160 resolveClass(c); 161 } 162 return c; 163 } 164 165 if (dbg) log.bug("Delegating to thread-context classloader to load class: ", name); 166 //we cannot use the parent classloader because it will not have 167 //classes inside of WEB-INF (the parent classloader is a jvm level 168 //classloader). We need to use the servlet containers classloader 169 //instead. 170 //return super.loadClass(name, resolve); 171 return Thread.currentThread().getContextClassLoader().loadClass(name); 172 } 173 174Class readClassFromDisk(File classfile) throws ClassNotFoundException 175 { 176 Argcheck.istrue(classfile.exists(), "The specified file does not exist: " + classfile.getAbsolutePath()); 177 178 byte[] buf = null; 179 180 try { 181 if (dbg) log.bug("Using filename:", classfile); 182 183 buf = IOUtil.fileToByteArray(classfile); 184 185 if (buf == null) { 186 throw new ClassNotFoundException(classfile.getPath()); 187 } 188 189 } 190 catch (Exception e) { 191 throw new ClassNotFoundException(classfile.getPath()); 192 } 193 194 /* 195 definePackage(Page.PACKAGE_NAME, null, null, null, null, null, null, null); 196 197 this works ONCE per instance of the classloader -- will crap out 198 if we use the same instance to define more than 1 class. defining 199 a package is not needed at all, but may be nice to have for logging 200 purposes/setting logging levels. If we have an inner class, this 201 method will be invoked more than once (one per inner class which 202 uses this same classloader). 203 204 All of this is very obscure and if this causes problems, remove. 205 */ 206 if (getPackage(TemplatePage.PACKAGE_NAME) == null) 207 definePackage(TemplatePage.PACKAGE_NAME, null, null, null, null, null, null, null); 208 209 Class c = defineClass(null, buf, 0, buf.length); 210 211 return c; 212 } 213 214public static void main (String args[]) throws Exception 215 { 216 Args myargs = new Args(args); 217 myargs.setUsage("java " + ClassUtil.getClassName() + 218 " -file full-path-to-class-file"); 219 String page = TemplatePage.PACKAGE_NAME + "." + myargs.getRequired("file"); 220 221 TemplateClassLoader cl = new TemplateClassLoader(); 222 cl.log.setLevel("debug"); 223 224 System.out.println("Loading java.lang.object: "); 225 Class c = cl.loadClass("java.lang.Object"); 226 System.out.println(ClassUtil.getClassLoaderInfo(c)); 227 228 System.out.println("Loading " + page); 229 c = cl.loadClass(page); 230 System.out.println(ClassUtil.getClassLoaderInfo(c)); 231 System.out.println("Package for this class: " + c.getPackage()); 232 } 233 234}