| 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