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>&lt;scratchdir&gt;/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}