001/* ---------------------------------------------------------------------------- 002 The Kiwi Toolkit - A Java Class Library 003 Copyright (C) 1998-2004 Mark A. Lindner 004 005 This library is free software; you can redistribute it and/or 006 modify it under the terms of the GNU General Public License as 007 published by the Free Software Foundation; either version 2 of the 008 License, or (at your option) any later version. 009 010 This library is distributed in the hope that it will be useful, 011 but WITHOUT ANY WARRANTY; without even the implied warranty of 012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 General Public License for more details. 014 015 You should have received a copy of the GNU General Public License 016 along with this library; if not, write to the Free Software 017 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 018 02111-1307, USA. 019 020 The author may be contacted at: mark_a_lindner@yahoo.com 021 ---------------------------------------------------------------------------- 022 $Log: PluginClassLoader.java,v $ 023 Revision 1.5 2004/05/12 18:03:42 markl 024 javadoc updates 025 026 Revision 1.4 2004/03/15 06:47:46 markl 027 Major rewrite to allow multiple instantiations of a given plugin, and 028 reload capability. 029 030 Revision 1.3 2003/01/19 09:31:22 markl 031 Javadoc & comment header updates. 032 033 Revision 1.2 2001/03/18 06:41:21 markl 034 Rewrite to simplify code and fix bugs. 035 036 Revision 1.1 2001/03/12 10:20:59 markl 037 New classes. 038 ---------------------------------------------------------------------------- 039*/ 040 041package kiwi.util.plugin; 042 043import java.io.*; 044import java.util.*; 045import java.util.jar.*; 046 047/* The internal class loader for plugins. 048 * 049 * The plugin will be loaded in its own namespace. It will have access to all 050 * classes in its own namespace, AND access to any class that belongs to any 051 * of the _restrictedPackages_ list, BUT NOT access to any class that belongs 052 * to any of the _forbiddenPackages_ list. 053 * 054 * - If asked to load a class that is from a restricted package, the class 055 * loader delegates to the system class loader. Packages "java.*' and 'javax.*' 056 * are automatically added to the restricted package list. This prevents the 057 * plugin from loading classes into those packages, or replacing system classes 058 * with its own in its namespace. 059 * 060 * - If asked to load a class that is from a forbidden package, the class 061 * loader throws a ClassNotFoundException. 062 * 063 * - If asked to load any other class other than those listed above, the class 064 * loader attempts to load the class by searching for it in the registered JAR 065 * files. 066 * 067 * @author Mark Lindner 068 */ 069 070class PluginClassLoader extends ClassLoader 071 { 072 private Vector forbiddenPackages; 073 private Vector restrictedPackages; 074 private Vector jars; 075 private Hashtable classCache; 076 077 /* 078 */ 079 080 PluginClassLoader(Vector forbiddenPackages, Vector restrictedPackages) 081 { 082 this.forbiddenPackages = forbiddenPackages; 083 this.restrictedPackages = restrictedPackages; 084 085 classCache = new Hashtable(); 086 jars = new Vector(); 087 } 088 089 /* 090 */ 091 092 void addJarFile(JarFile file) 093 { 094 synchronized(jars) 095 { 096 if((file != null) && ! jars.contains(file)) 097 jars.addElement(file); 098 } 099 } 100 101 /** 102 */ 103 104 public synchronized Class loadClass(String className, boolean resolve) 105 throws ClassNotFoundException 106 { 107 Class result = null; 108 109 // extract package name 110 111 int ind = className.lastIndexOf('.'); 112 if(ind < 0) 113 return(null); // won't support classes in unnamed packages 114 115 String classPackage = className.substring(0, ind); 116 117 // first check our cache 118 119 result = findLoadedClass(className); 120 121 // not in cache... 122 123 if(result == null) 124 { 125 126 // try to load it from the system class loader first, but only if it's 127 // not from a forbidden package 128 129 if(! isForbiddenPackage(classPackage)) 130 { 131 try 132 { 133 return(findSystemClass(className)); 134 } 135 catch(ClassNotFoundException ex) 136 { 137 /* ignore & continue */ 138 } 139 140 // no luck, so scan through JAR files looking for the class, but only 141 // if it's not a restricted class (or a forbidden class) 142 143 if(! isRestrictedPackage(classPackage)) 144 { 145 String path = classNameToPath(className); 146 147 Enumeration e = jars.elements(); 148 149 while(e.hasMoreElements()) 150 { 151 JarFile jar = (JarFile)e.nextElement(); 152 JarEntry entry = (JarEntry)jar.getEntry(path); 153 if(entry == null) 154 continue; // move on to next JAR file 155 156 // found it! so let's load it... 157 158 BufferedInputStream ins = null; 159 int r = 0, size = 0; 160 byte b[] = null; 161 162 try 163 { 164 ins = new BufferedInputStream(jar.getInputStream(entry)); 165 size = (int)entry.getSize(); 166 167 b = new byte[size]; 168 r = ins.read(b, 0, size); 169 } 170 catch(IOException ex) 171 { 172 r = -1; 173 } 174 175 if(ins != null) 176 { 177 try 178 { 179 ins.close(); 180 } 181 catch(IOException ex) { /* ignore */ } 182 } 183 184 if(r != size) // got less or more bytes than we expected? 185 throw(new ClassFormatError(className)); 186 187 result = defineClass(className, b, 0, size); 188 break; 189 } 190 } 191 } 192 } 193 194 if(result == null) 195 throw(new ClassNotFoundException(className)); 196 197 if(resolve) 198 resolveClass(result); 199 200 return(result); 201 } 202 203 /* 204 */ 205 206 private boolean isForbiddenPackage(String packageName) 207 { 208 return(findPackage(packageName, forbiddenPackages)); 209 } 210 211 /* 212 */ 213 214 private boolean isRestrictedPackage(String packageName) 215 { 216 return(findPackage(packageName, restrictedPackages)); 217 } 218 219 /* 220 */ 221 222 private boolean findPackage(String packageName, Vector packageList) 223 { 224 synchronized(packageList) 225 { 226 Enumeration e = packageList.elements(); 227 228 while(e.hasMoreElements()) 229 { 230 String pkg = (String)e.nextElement(); 231 232 if(pkg.endsWith(".*")) 233 { 234 if(packageName.startsWith(pkg.substring(0, pkg.length() - 1))) 235 return(true); 236 } 237 else if(pkg.equals(packageName)) 238 return(true); 239 } 240 } 241 242 return(false); 243 } 244 245 /** 246 */ 247 248 public synchronized InputStream getResourceAsStream(String name) 249 { 250 /* Scan through JAR files looking for the resource */ 251 252 Enumeration e = jars.elements(); 253 while(e.hasMoreElements()) 254 { 255 JarFile jar = (JarFile)e.nextElement(); 256 JarEntry entry = jar.getJarEntry(name); 257 258 if(entry != null) 259 { 260 try 261 { 262 return(jar.getInputStream(entry)); 263 } 264 catch(IOException ex) 265 { 266 /* ignore error, & continue */ 267 } 268 } 269 } 270 271 return(null); 272 } 273 274 /* convert a class name to its corresponding JAR entry name 275 */ 276 277 static String classNameToPath(String className) 278 { 279 return(className.replace('.', '/') + ".class"); 280 } 281 282 /* convert a JAR entry name to its corresponding class name 283 */ 284 285 static String pathToClassName(String path) 286 { 287 return(path.substring(0, path.length() - 6).replace('/', '.')); 288 } 289 290 } 291 292/* end of source file */