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