Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
CWClassLoader |
|
| 3.2857142857142856;3.286 | ||||
CWClassLoader$PrivilegedLoader |
|
| 3.2857142857142856;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 | } |