Coverage Report - org.crosswire.common.util.CWClassLoader
 
Classes in this File Line Coverage Branch Coverage Complexity
CWClassLoader
0%
0/79
0%
0/56
3.286
CWClassLoader$PrivilegedLoader
0%
0/4
N/A
3.286
 
 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  0
     CWClassLoader(Class<?> resourceOwner) {
 46  0
         owner = resourceOwner;
 47  0
     }
 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  0
     CWClassLoader() {
 55  0
         owner = CallContext.getCallingClass();
 56  0
     }
 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  0
         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  0
         Class<? extends Object> resourceOwner = CallContext.getCallingClass();
 80  0
         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  0
         URL resource = null;
 89  0
         if (search == null || search.length() == 0) {
 90  0
             return resource;
 91  
         }
 92  
 
 93  
         // First look for it with an absolute path
 94  
         // This allows developer overrides
 95  0
         if (search.charAt(0) != '/') {
 96  0
             resource = findResource('/' + search);
 97  
         }
 98  
 
 99  0
         if (resource == null) {
 100  
             // Look for it in the class's package.
 101  0
             String newsearch = adjustPackageSearch(search);
 102  0
             if (!search.equals(newsearch)) {
 103  0
                 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  0
         if (resource == null) {
 111  
             // Look for it in the class's package.
 112  0
             String newsearch = adjustPathSearch(search);
 113  0
             if (!search.equals(newsearch)) {
 114  0
                 resource = findResource(newsearch);
 115  
             }
 116  
         }
 117  
 
 118  
         // See if it can be found in a home directory
 119  0
         if (resource == null) {
 120  0
             URI homeResource = CWClassLoader.findHomeResource(search);
 121  0
             if (homeResource != null) {
 122  0
                 resource = NetUtil.toURL(homeResource);
 123  
             }
 124  
         }
 125  
 
 126  
         // See if it can be found by its own class.
 127  0
         if (resource == null) {
 128  0
             resource = owner.getResource(search);
 129  
         }
 130  
 
 131  
         // Try the appropriate class loader
 132  0
         if (resource == null) {
 133  0
             resource = getClassLoader().getResource(search);
 134  
         }
 135  
 
 136  
         // Try the bootstrap and the system loader
 137  0
         if (resource == null) {
 138  0
             resource = ClassLoader.getSystemResource(search);
 139  
         }
 140  
 
 141  
         // For good measure let the super class try to find it.
 142  0
         if (resource == null) {
 143  0
             resource = super.findResource(search);
 144  
         }
 145  
 
 146  0
         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  0
         String search = aSearch;
 158  
         // If it has embedded '/' there is nothing to do.
 159  0
         if (search.indexOf('/', 1) == -1) {
 160  0
             String className = owner.getName();
 161  0
             String pkgPrefix = className.substring(0, className.lastIndexOf('.') + 1);
 162  
 
 163  0
             if (search.charAt(0) == '/') {
 164  0
                 String part = search.substring(1);
 165  0
                 if (!part.startsWith(pkgPrefix)) {
 166  0
                     search = '/' + pkgPrefix + part;
 167  
                 }
 168  0
             } else {
 169  0
                 if (!search.startsWith(pkgPrefix)) {
 170  0
                     search = pkgPrefix + search;
 171  
                 }
 172  
             }
 173  
         }
 174  
 
 175  0
         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  0
         String search = aSearch;
 186  0
         if (search.indexOf('/', 1) != -1) {
 187  
             // Change all but a leading '/' to '.'
 188  0
             if (search.charAt(0) == '/') {
 189  0
                 search = '/' + search.substring(1).replace('/', '.');
 190  
             } else {
 191  0
                 search = search.replace('/', '.');
 192  
             }
 193  
         }
 194  0
         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  0
         ClassLoader loader = pickLoader(Thread.currentThread().getContextClassLoader(), owner.getClassLoader());
 206  0
         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  0
         ClassLoader loader = loader2;
 221  0
         if (loader1 != loader2) {
 222  0
             loader = loader1;
 223  0
             if (loader1 == null) {
 224  0
                 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  0
                 for (ClassLoader curloader = loader2; curloader != null; curloader = curloader.getParent()) {
 229  0
                     if (curloader == loader1) {
 230  0
                         loader = loader2;
 231  0
                         break;
 232  
                     }
 233  
                 }
 234  
             }
 235  
         }
 236  0
         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  0
         if (i > 0 && i < homes.length) {
 248  0
             return homes[i];
 249  
         }
 250  0
         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  0
         homes = new URI[newHomes.length];
 262  0
         System.arraycopy(newHomes, 0, homes, 0, newHomes.length);
 263  0
     }
 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  0
         if (homes == null) {
 275  0
             return null;
 276  
         }
 277  
 
 278  0
         for (int i = 0; i < homes.length; i++) {
 279  0
             URI homeURI = homes[i];
 280  
 
 281  0
             URI override = NetUtil.lengthenURI(homeURI, search);
 282  
 
 283  
             // Make sure the file exists and can be read
 284  0
             if (NetUtil.canRead(override)) {
 285  0
                 return override;
 286  
             }
 287  
         }
 288  
 
 289  0
         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  0
         PrivilegedLoader(Class<?> resourceOwner) {
 309  0
             owningClass = resourceOwner;
 310  0
         }
 311  
 
 312  
         /* (non-Javadoc)
 313  
          * @see java.security.PrivilegedAction#run()
 314  
          */
 315  
         public T run() {
 316  0
             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  
 }