001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019 020package icy.plugin.classloader; 021 022import icy.plugin.classloader.exception.JclException; 023import icy.plugin.classloader.exception.ResourceNotFoundException; 024import icy.system.IcyExceptionHandler; 025 026import java.io.ByteArrayInputStream; 027import java.io.IOException; 028import java.io.InputStream; 029import java.net.URL; 030import java.util.Collections; 031import java.util.Enumeration; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036import java.util.logging.Level; 037import java.util.logging.Logger; 038 039/** 040 * Reads the class bytes from jar files and other resources using 041 * ClasspathResources 042 * 043 * @author Kamran Zafar 044 * @author Stephane Dallongeville 045 */ 046@SuppressWarnings("rawtypes") 047public class JarClassLoader extends AbstractClassLoader 048{ 049 /** 050 * Class cache 051 */ 052 protected final Map<String, Class> loadedClasses; 053 054 protected final ClasspathResources classpathResources; 055 private char classNameReplacementChar; 056 private final ProxyClassLoader localLoader = new LocalLoader(); 057 058 private static Logger logger = Logger.getLogger(JarClassLoader.class.getName()); 059 060 public JarClassLoader(ClassLoader parent) 061 { 062 super(parent); 063 064 classpathResources = new ClasspathResources(); 065 loadedClasses = Collections.synchronizedMap(new HashMap<String, Class>()); 066 067 addLoader(localLoader); 068 } 069 070 public JarClassLoader() 071 { 072 this(getSystemClassLoader()); 073 } 074 075 /** 076 * Loads classes from different sources 077 * 078 * @param sources 079 */ 080 public JarClassLoader(Object[] sources) 081 { 082 this(); 083 084 addAll(sources); 085 } 086 087 /** 088 * Loads classes from different sources 089 * 090 * @param sources 091 */ 092 public JarClassLoader(List sources) 093 { 094 this(); 095 096 addAll(sources); 097 } 098 099 /** 100 * Add all jar/class sources 101 * 102 * @param sources 103 */ 104 public void addAll(Object[] sources) 105 { 106 for (Object source : sources) 107 add(source); 108 } 109 110 /** 111 * Add all jar/class sources 112 * 113 * @param sources 114 */ 115 public void addAll(List sources) 116 { 117 for (Object source : sources) 118 add(source); 119 } 120 121 /** 122 * Loads local/remote source 123 * 124 * @param source 125 */ 126 public void add(Object source) 127 { 128 if (source instanceof InputStream) 129 throw new JclException("Unsupported resource type"); 130 else if (source instanceof URL) 131 add((URL) source); 132 else if (source instanceof String) 133 add((String) source); 134 else 135 throw new JclException("Unknown Resource type"); 136 137 } 138 139 /** 140 * Loads local/remote resource 141 * 142 * @param resourceName 143 */ 144 public void add(String resourceName) 145 { 146 classpathResources.loadResource(resourceName); 147 } 148 149 /** 150 * Loads classes from InputStream. 151 * 152 * @deprecated Not anymore supported (we need URL for getResource(..) method) 153 */ 154 @Deprecated 155 public void add(InputStream jarStream) 156 { 157 // classpathResources.loadJar(jarStream); 158 } 159 160 /** 161 * Loads local/remote resource 162 * 163 * @param url 164 */ 165 public void add(URL url) 166 { 167 classpathResources.loadResource(url); 168 } 169 170 /** 171 * Release all loaded resources and classes. 172 * The ClassLoader cannot be used anymore to load any new resource. 173 */ 174 public void unloadAll() 175 { 176 // unload resources 177 classpathResources.entryContents.clear(); 178 // unload classes 179 loadedClasses.clear(); 180 } 181 182 /** 183 * Reads the class bytes from different local and remote resources using 184 * ClasspathResources 185 * 186 * @param className 187 * @return byte[] 188 * @throws IOException 189 */ 190 protected byte[] getClassBytes(String className) throws IOException 191 { 192 return classpathResources.getResourceContent(formatClassName(className)); 193 } 194 195 /** 196 * Attempts to unload class, it only unloads the locally loaded classes by 197 * JCL 198 * 199 * @param className 200 */ 201 public void unloadClass(String className) 202 { 203 if (logger.isLoggable(Level.FINEST)) 204 logger.finest("Unloading class " + className); 205 206 if (loadedClasses.containsKey(className)) 207 { 208 if (logger.isLoggable(Level.FINEST)) 209 logger.finest("Removing loaded class " + className); 210 loadedClasses.remove(className); 211 try 212 { 213 classpathResources.unload(formatClassName(className)); 214 } 215 catch (ResourceNotFoundException e) 216 { 217 throw new JclException("Something is very wrong!!!" 218 + "The locally loaded classes must be in synch with ClasspathResources", e); 219 } 220 } 221 else 222 { 223 try 224 { 225 classpathResources.unload(formatClassName(className)); 226 } 227 catch (ResourceNotFoundException e) 228 { 229 throw new JclException("Class could not be unloaded " 230 + "[Possible reason: Class belongs to the system]", e); 231 } 232 } 233 } 234 235 /** 236 * @param className 237 * @return String 238 */ 239 protected String formatClassName(String className) 240 { 241 String cname = className.replace('/', '~'); 242 243 if (classNameReplacementChar == '\u0000') 244 // '/' is used to map the package to the path 245 cname = cname.replace('.', '/') + ".class"; 246 else 247 // Replace '.' with custom char, such as '_' 248 cname = cname.replace('.', classNameReplacementChar) + ".class"; 249 250 return cname.replace('~', '/'); 251 } 252 253 /** 254 * Local class loader 255 */ 256 class LocalLoader extends ProxyClassLoader 257 { 258 private final Logger logger = Logger.getLogger(LocalLoader.class.getName()); 259 260 public LocalLoader() 261 { 262 super(50); 263 264 enabled = Configuration.isLocalLoaderEnabled(); 265 } 266 267 @Override 268 public ClassLoader getLoader() 269 { 270 return JarClassLoader.this; 271 } 272 273 @Override 274 public Class loadClass(String className, boolean resolveIt) throws ClassNotFoundException, ClassFormatError 275 { 276 Class result = null; 277 byte[] classBytes; 278 279 result = loadedClasses.get(className); 280 if (result != null) 281 { 282 if (logger.isLoggable(Level.FINEST)) 283 logger.finest("Returning local loaded class [" + className + "] from cache"); 284 return result; 285 } 286 287 // try to find from already loaded class (by other method) 288 result = findLoadedClass(className); 289 // not loaded ? 290 if (result == null) 291 { 292 try 293 { 294 classBytes = getClassBytes(className); 295 } 296 catch (IOException e) 297 { 298 // we got a severe error here --> throw an exception 299 throw new ClassNotFoundException(className, e); 300 } 301 302 if (classBytes == null) 303 return null; 304 305 result = defineClass(className, classBytes, 0, classBytes.length); 306 307 if (result == null) 308 return null; 309 } 310 311 /* 312 * Preserve package name. 313 */ 314 if (result.getPackage() == null) 315 { 316 int lastDotIndex = className.lastIndexOf('.'); 317 String packageName = (lastDotIndex >= 0) ? className.substring(0, lastDotIndex) : ""; 318 definePackage(packageName, null, null, null, null, null, null, null); 319 } 320 321 if (resolveIt) 322 resolveClass(result); 323 324 loadedClasses.put(className, result); 325 if (logger.isLoggable(Level.FINEST)) 326 logger.finest("Return new local loaded class " + className); 327 328 return result; 329 } 330 331 @Override 332 public InputStream getResourceAsStream(String name) 333 { 334 try 335 { 336 byte[] arr = classpathResources.getResourceContent(name); 337 338 if (arr != null) 339 { 340 if (logger.isLoggable(Level.FINEST)) 341 logger.finest("Returning newly loaded resource " + name); 342 343 return new ByteArrayInputStream(arr); 344 } 345 } 346 catch (IOException e) 347 { 348 IcyExceptionHandler.showErrorMessage(e, false, true); 349 } 350 351 return null; 352 } 353 354 @Override 355 public URL getResource(String name) 356 { 357 URL url = classpathResources.getResource(name); 358 359 if (url != null) 360 { 361 if (logger.isLoggable(Level.FINEST)) 362 logger.finest("Returning newly loaded resource " + name); 363 364 return url; 365 } 366 367 return null; 368 } 369 370 @Override 371 public Enumeration<URL> getResources(String name) throws IOException 372 { 373 final URL url = getResource(name); 374 375 return new Enumeration<URL>() 376 { 377 boolean hasMore = (url != null); 378 379 @Override 380 public boolean hasMoreElements() 381 { 382 return hasMore; 383 } 384 385 @Override 386 public URL nextElement() 387 { 388 if (hasMore) 389 { 390 hasMore = false; 391 return url; 392 } 393 394 return null; 395 } 396 }; 397 } 398 } 399 400 public char getClassNameReplacementChar() 401 { 402 return classNameReplacementChar; 403 } 404 405 public void setClassNameReplacementChar(char classNameReplacementChar) 406 { 407 this.classNameReplacementChar = classNameReplacementChar; 408 } 409 410 /** 411 * Returns an immutable Set of all resources name 412 */ 413 public Set<String> getResourcesName() 414 { 415 return classpathResources.getResourcesName(); 416 } 417 418 /** 419 * Returns an immutable Map of all resources 420 */ 421 public Map<String, URL> getResources() 422 { 423 return classpathResources.getResources(); 424 } 425 426 /** 427 * Returns all currently loaded classes and resources. 428 */ 429 public Map<String, byte[]> getLoadedResources() 430 { 431 return classpathResources.getLoadedResources(); 432 } 433 434 /** 435 * @return Local JCL ProxyClassLoader 436 */ 437 public ProxyClassLoader getLocalLoader() 438 { 439 return localLoader; 440 } 441 442 /** 443 * Returns all JCL-loaded classes as an immutable Map 444 * 445 * @return Map 446 */ 447 public Map<String, Class> getLoadedClasses() 448 { 449 return Collections.unmodifiableMap(loadedClasses); 450 } 451}