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}