CWClassLoader.java |
1 /** 2 * Distribution License: 3 * JSword is free software; you can redistribute it and/or modify it under 4 * the terms of the GNU Lesser General Public License, version 2.1 or later 5 * as published by the Free Software Foundation. This program is distributed 6 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even 7 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 8 * See the GNU Lesser General Public License for more details. 9 * 10 * The License is available on the internet at: 11 * http://www.gnu.org/copyleft/lgpl.html 12 * or by writing to: 13 * Free Software Foundation, Inc. 14 * 59 Temple Place - Suite 330 15 * Boston, MA 02111-1307, USA 16 * 17 * © CrossWire Bible Society, 2005 - 2016 18 * 19 */ 20 package org.crosswire.common.util; 21 22 import java.net.URI; 23 import java.net.URL; 24 import java.security.AccessController; 25 import java.security.PrivilegedAction; 26 27 /** 28 * CWClassLoader extends the regular class loader by using looking in more 29 * places. This is needed so that ResourceBundle can find resources that are not 30 * held in the same package as the class. This is expressed as a list of 31 * locations, called homes, that the program will look in. 32 * 33 * @see gnu.lgpl.License The GNU Lesser General Public License for details. 34 * @author DM Smith 35 */ 36 public final class CWClassLoader extends ClassLoader { 37 /** 38 * Creates a class loader that finds resources for the supplied class that 39 * may not be in the class' package. You can use this within base classes by 40 * passing getClass() to load resources for a derived class. 41 * 42 * @param resourceOwner 43 * is the owner of the resource 44 */ 45 CWClassLoader(Class<?> resourceOwner) { 46 owner = resourceOwner; 47 } 48 49 /** 50 * Creates a class loader that finds resources for the calling class that 51 * may not be in the class' package. Use this only within classes that are 52 * directly looking up their resources. 53 */ 54 CWClassLoader() { 55 owner = CallContext.getCallingClass(); 56 } 57 58 /** 59 * Creates a privileged class loader that finds resources for the supplied 60 * class that may not be in the class' package. You can use this within base 61 * classes by passing getClass() to load resources for a derived class. 62 * 63 * @param resourceOwner 64 * is the owner of the resource 65 * @return the CrossWire Class Loader 66 */ 67 public static CWClassLoader instance(Class<?> resourceOwner) { 68 return AccessController.doPrivileged(new PrivilegedLoader<CWClassLoader>(resourceOwner)); 69 } 70 71 /** 72 * Creates a privileged class loader that finds resources for the calling 73 * class that may not be in the class' package. Use this only within classes 74 * that are directly looking up their resources. 75 * 76 * @return the CrossWire Class Loader 77 */ 78 public static CWClassLoader instance() { 79 Class<? extends Object> resourceOwner = CallContext.getCallingClass(); 80 return instance(resourceOwner); 81 } 82 83 /* (non-Javadoc) 84 * @see java.lang.ClassLoader#findResource(java.lang.String) 85 */ 86 @Override 87 public URL findResource(String search) { 88 URL resource = null; 89 if (search == null || search.length() == 0) { 90 return resource; 91 } 92 93 // First look for it with an absolute path 94 // This allows developer overrides 95 if (search.charAt(0) != '/') { 96 resource = findResource('/' + search); 97 } 98 99 if (resource == null) { 100 // Look for it in the class's package. 101 String newsearch = adjustPackageSearch(search); 102 if (!search.equals(newsearch)) { 103 resource = findResource(newsearch); 104 } 105 } 106 107 // Sometimes it comes in with '/' inside of it. 108 // So look for it as a file with '.' in the name 109 // This is the form that will find files in the resource.jar 110 if (resource == null) { 111 // Look for it in the class's package. 112 String newsearch = adjustPathSearch(search); 113 if (!search.equals(newsearch)) { 114 resource = findResource(newsearch); 115 } 116 } 117 118 // See if it can be found in a home directory 119 if (resource == null) { 120 URI homeResource = CWClassLoader.findHomeResource(search); 121 if (homeResource != null) { 122 resource = NetUtil.toURL(homeResource); 123 } 124 } 125 126 // See if it can be found by its own class. 127 if (resource == null) { 128 resource = owner.getResource(search); 129 } 130 131 // Try the appropriate class loader 132 if (resource == null) { 133 resource = getClassLoader().getResource(search); 134 } 135 136 // Try the bootstrap and the system loader 137 if (resource == null) { 138 resource = ClassLoader.getSystemResource(search); 139 } 140 141 // For good measure let the super class try to find it. 142 if (resource == null) { 143 resource = super.findResource(search); 144 } 145 146 return resource; 147 } 148 149 /** 150 * Prefix the search with a package prefix, if not already. Skip a leading 151 * '/' if present. 152 * 153 * @param aSearch the search to adjust 154 * @return the adjusted search 155 */ 156 private String adjustPackageSearch(String aSearch) { 157 String search = aSearch; 158 // If it has embedded '/' there is nothing to do. 159 if (search.indexOf('/', 1) == -1) { 160 String className = owner.getName(); 161 String pkgPrefix = className.substring(0, className.lastIndexOf('.') + 1); 162 163 if (search.charAt(0) == '/') { 164 String part = search.substring(1); 165 if (!part.startsWith(pkgPrefix)) { 166 search = '/' + pkgPrefix + part; 167 } 168 } else { 169 if (!search.startsWith(pkgPrefix)) { 170 search = pkgPrefix + search; 171 } 172 } 173 } 174 175 return search; 176 } 177 178 /** 179 * Change all but a leading '/' to '.' 180 * 181 * @param aSearch the search to adjust 182 * @return the adjusted search 183 */ 184 private String adjustPathSearch(String aSearch) { 185 String search = aSearch; 186 if (search.indexOf('/', 1) != -1) { 187 // Change all but a leading '/' to '.' 188 if (search.charAt(0) == '/') { 189 search = '/' + search.substring(1).replace('/', '.'); 190 } else { 191 search = search.replace('/', '.'); 192 } 193 } 194 return search; 195 } 196 197 /** 198 * Pick the best class loader 199 * 200 * @return the class loader 201 */ 202 public ClassLoader getClassLoader() { 203 // Choose the child loader as it will use the parent if need be 204 // If they are not related then choose the context loader 205 ClassLoader loader = pickLoader(Thread.currentThread().getContextClassLoader(), owner.getClassLoader()); 206 return pickLoader(loader, ClassLoader.getSystemClassLoader()); 207 } 208 209 /** 210 * Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if 211 * 'loader1'=='loader2']. Of course, this works only for classloaders that 212 * set their parent pointers correctly. 'null' is interpreted as the 213 * primordial loader [i.e., everybody's parent]. 214 * 215 * @param loader1 a class loader to consider 216 * @param loader2 a class loader to consider 217 * @return one of the class loaders 218 */ 219 private static ClassLoader pickLoader(ClassLoader loader1, ClassLoader loader2) { 220 ClassLoader loader = loader2; 221 if (loader1 != loader2) { 222 loader = loader1; 223 if (loader1 == null) { 224 loader = loader2; 225 } else { 226 // Is loader2 a descendant of loader1? 227 // It is if we can walk up to the top and find it. 228 for (ClassLoader curloader = loader2; curloader != null; curloader = curloader.getParent()) { 229 if (curloader == loader1) { 230 loader = loader2; 231 break; 232 } 233 } 234 } 235 } 236 return loader; 237 } 238 239 /** 240 * If the application has set the homes, it will return the application's 241 * requested home directory, otherwise it returns null. 242 * 243 * @param i get the i-th home 244 * @return Returns the home. 245 */ 246 public static synchronized URI getHome(int i) { 247 if (i > 0 && i < homes.length) { 248 return homes[i]; 249 } 250 return null; 251 } 252 253 /** 254 * Establish the applications home directory from where additional resources 255 * can be found. URL is expected to end with the directory name, not '/'. 256 * 257 * @param newHomes 258 * The home to set. 259 */ 260 public static synchronized void setHome(URI[] newHomes) { 261 homes = new URI[newHomes.length]; 262 System.arraycopy(newHomes, 0, homes, 0, newHomes.length); 263 } 264 265 /** 266 * Look for the resource in the home directories, returning the first 267 * readable file. 268 * 269 * @param search 270 * must be non-null, non-empty 271 * @return the URI of the home of the resource 272 */ 273 public static URI findHomeResource(String search) { 274 if (homes == null) { 275 return null; 276 } 277 278 for (int i = 0; i < homes.length; i++) { 279 URI homeURI = homes[i]; 280 281 URI override = NetUtil.lengthenURI(homeURI, search); 282 283 // Make sure the file exists and can be read 284 if (NetUtil.canRead(override)) { 285 return override; 286 } 287 } 288 289 return null; 290 } 291 292 /** 293 * PrivilegedLoader creates a CWClassLoader if it is able to obtain java 294 * security permissions to do so. 295 * 296 * @param <T> the type 297 */ 298 private static class PrivilegedLoader<T> implements PrivilegedAction<T> { 299 /** 300 * Creates a privileged class loader that finds resources for the 301 * supplied class that may not be in the class' package. You can use 302 * this within base classes by passing getClass() to load resources for 303 * a derived class. 304 * 305 * @param resourceOwner 306 * is the owner of the resource 307 */ 308 PrivilegedLoader(Class<?> resourceOwner) { 309 owningClass = resourceOwner; 310 } 311 312 /* (non-Javadoc) 313 * @see java.security.PrivilegedAction#run() 314 */ 315 public T run() { 316 return (T) new CWClassLoader(owningClass); 317 } 318 319 private Class<?> owningClass; 320 } 321 322 /** 323 * The class to which the resources belong 324 */ 325 private Class<?> owner; 326 327 /** 328 * Notion of a project's home from where additional resources can be found. 329 */ 330 private static URI[] homes; 331 } 332