| 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 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