1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License")
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.jguiraffe.locators;
17
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.net.MalformedURLException;
23 import java.net.URL;
24
25 /**
26 * <p>
27 * A helper class for locating resources.
28 * </p>
29 * <p>
30 * This class provides functionality for locating resources like configuration
31 * files, which can be specified in multiple ways:
32 * </p>
33 * <p>
34 * <ul>
35 * <li>as absolute URLs.</li>
36 * <li>as absolute or relative files.</li>
37 * <li>as resources in the application's class path.</li>
38 * <li>through {@link Locator} objects.</li>
39 * </ul>
40 * </p>
41 *
42 * @author Oliver Heger
43 * @version $Id: LocatorUtils.java 211 2012-07-10 19:49:13Z oheger $
44 */
45 public final class LocatorUtils
46 {
47 /** Constant for the slash as prefix for resources.*/
48 private static final String SLASH = "/";
49
50 /** Constant for the buffer size for string generation.*/
51 private static final int BUF_SIZE = 20;
52
53 /**
54 * Private constructor, so no instance can be created.
55 */
56 private LocatorUtils()
57 {
58 }
59
60 /**
61 * Returns the URL for the given file. This is a convenience method that
62 * takes care for transforming the file to an URI and the URI eventually to
63 * a URL. If a <b>null</b> parameter is passed in, result will also be
64 * <b>null</b>.
65 *
66 * @param file the file to be transformed
67 * @return the corresponding URL
68 * @throws LocatorException if the transformation fails
69 */
70 public static URL fileURL(File file)
71 {
72 if (file == null)
73 {
74 return null;
75 }
76
77 try
78 {
79 return file.toURI().toURL();
80 }
81 catch (MalformedURLException mex)
82 {
83 throw new LocatorException(mex);
84 }
85 }
86
87 /**
88 * Tries to locate a resource in the class path using the specified class
89 * loader. If the {@code ClassLoader} parameter is not <b>null</b>, this
90 * class loader is tried first. If this fails, this method tries the
91 * context, the default, and the system class loader (in this order) to find
92 * the resource in the class path. Search stops as soon as the resource is
93 * found.
94 *
95 * @param resource the name of the resource
96 * @param cl the class loader to use for looking up the resource (can be
97 * <b>null</b>)
98 * @return the URL to the resource or <b>null</b> if it cannot be resolved
99 * @since 1.2
100 */
101 public static URL locateResource(String resource, ClassLoader cl)
102 {
103 if (resource == null)
104 {
105 return null;
106 }
107
108 URL result = null;
109 if (cl != null)
110 {
111 result = cl.getResource(resource);
112 }
113
114 if (result == null)
115 {
116 ClassLoader ctxCL = Thread.currentThread().getContextClassLoader();
117 if (ctxCL != null)
118 {
119 result = ctxCL.getResource(resource);
120 }
121 }
122
123 if (result == null)
124 {
125 result = LocatorUtils.class.getResource(resource);
126 }
127
128 if (result == null)
129 {
130 result = ClassLoader.getSystemResource(resource);
131 }
132
133 if (result == null && !resource.startsWith(SLASH))
134 {
135 result = locateResource(SLASH + resource, cl);
136 }
137 return result;
138 }
139
140 /**
141 * Tries to locate a resource in the class path. No specific class loader is
142 * provided, so this method tries the context, the default, and the system
143 * class loader (in this order) to find the resource in the class path.
144 *
145 * @param resource the name of the resource
146 * @return the URL to the resource or <b>null</b> if it cannot be resolved
147 * @see #locateResource(String, ClassLoader)
148 */
149 public static URL locateResource(String resource)
150 {
151 return locateResource(resource, null);
152 }
153
154 /**
155 * Locates a file using a string, which can be either a full URL or a file
156 * name. Some variants are tried until a valid resource can be found.
157 *
158 * @param url the URL of the file to be located
159 * @return the full URL to this file or <b>null</b> if it cannot be found
160 */
161 public static URL locateURL(String url)
162 {
163 if (url != null)
164 {
165 try
166 {
167 return new URL(url);
168 }
169 catch (MalformedURLException mex)
170 {
171 // no valid URL, check if it is a file
172 File f = new File(url);
173 if (f.exists())
174 {
175 return fileURL(f);
176 }
177 }
178 }
179
180 return null;
181 }
182
183 /**
184 * Locates a resource either from a URL or a class path resource, returning
185 * <b>null</b> if the resource cannot be resolved. This method combines the
186 * methods {@code locateURL()} and {@code locateResource()}. If a URL is
187 * defined, it is tried to be resolved. If this fails and a resource name is
188 * defined, this name is tried to be resolved. If both parameters are
189 * defined and valid, the URL takes precedence. If both attempts fail, the
190 * result is <b>null</b>. If a {@code ClassLoader} is specified, it is used
191 * during class path lookup as described at
192 * {@link #locateResource(String, ClassLoader)}.
193 *
194 * @param url specifies a URL
195 * @param name specifies a resource name
196 * @param cl an optional class loader for the class path lookup
197 * @return the resolved URL or <b>null</b> if the resource cannot be
198 * resolved
199 * @since 1.2
200 */
201 public static URL locate(String url, String name, ClassLoader cl)
202 {
203 URL result = locateURL(url);
204 if (result == null)
205 {
206 result = locateResource(name, cl);
207 }
208
209 return result;
210 }
211
212 /**
213 * Locates a resource either from a URL or a class path resource (using a
214 * default class loader), returning <b>null</b> if the resource cannot be
215 * resolved.
216 *
217 * @param url specifies a URL
218 * @param name specifies a resource name
219 * @return the resolved URL or <b>null</b>
220 * @see #locate(String, String, ClassLoader)
221 */
222 public static URL locate(String url, String name)
223 {
224 return locate(url, name, null);
225 }
226
227 /**
228 * Locates a resource either from a URL or a class path resource, throwing
229 * an exception if the resource cannot be resolved. This is analogous to
230 * {@code locate()}, but a failing lookup causes a {@code LocatorException}.
231 *
232 * @param url specifies a URL
233 * @param name specifies a resource name
234 * @param cl an optional class loader for the class path lookup
235 * @return the resolved URL
236 * @throws LocatorException if the resource cannot be resolved
237 * @since 1.2
238 */
239 public static URL locateEx(String url, String name, ClassLoader cl)
240 {
241 URL result = locate(url, name, cl);
242 if (result == null)
243 {
244 throw new LocatorException("Cannot resolve resource: URL = " + url
245 + ", resource name = " + name);
246 }
247 return result;
248 }
249
250 /**
251 * Locates a resource either from a URL or a class path resource (using a
252 * default class loader), throwing an exception if the resource cannot be
253 * resolved.
254 *
255 * @param url specifies a URL
256 * @param name specifies a resource name
257 * @return the resolved URL
258 * @throws LocatorException if the resource cannot be resolved
259 * @see #locateEx(String, String, ClassLoader)
260 */
261 public static URL locateEx(String url, String name)
262 {
263 return locateEx(url, name, null);
264 }
265
266 /**
267 * Obtains an input stream for the specified locator. This method will query
268 * the different methods of the locator until a result is found. From this
269 * result a stream will be created. The locator's methods are invoked in the
270 * following order (until a non <b>null</b> result is obtained):
271 * <ol>
272 * <li>{@code getInputStream()}</li>
273 * <li>{@code getFile()}</li>
274 * <li>{@code getURL()}</li>
275 * </ol>
276 *
277 * @param locator the locator
278 * @return the input stream for this locator
279 * @throws IOException if an IO error occurs
280 * @throws LocatorException if the locator throws an exception or no valid
281 * values are returned
282 */
283 public static InputStream openStream(Locator locator) throws IOException
284 {
285 if (locator == null)
286 {
287 throw new LocatorException("Locator must not be null!");
288 }
289
290 InputStream stream = locator.getInputStream();
291 if (stream != null)
292 {
293 return stream;
294 }
295 else
296 {
297 File file = locator.getFile();
298 if (file != null)
299 {
300 return new FileInputStream(file);
301 }
302 else
303 {
304 URL url = locator.getURL();
305 if (url == null)
306 {
307 throw new LocatorException("Locator returns only null!");
308 }
309 return url.openStream();
310 }
311 }
312 }
313
314 /**
315 * Creates a string representation of a {@code Locator} object. This
316 * string contains the fully qualified class name of the
317 * {@code Locator} (<em>class</em>), its identity hash code (
318 * <em>hash</em>), and the data passed to this method (<em>data</em>, which
319 * is locator specific). It has the following form: {@code class@hash[ data
320 * ]}. The concrete {@code Locator} implementations in this package use
321 * this method in the implementation of their {@code toString()}
322 * method.
323 *
324 * @param locator the {@code Locator} to be transformed to a string
325 * (must not be <b>null</b>)
326 * @param locatorData the data of this locator
327 * @return a string representation for this {@code Locator}
328 * @throws IllegalArgumentException if the locator is <b>null</b>
329 */
330 public static String locatorToString(Locator locator, String locatorData)
331 {
332 if (locator == null)
333 {
334 throw new IllegalArgumentException("Locator must not be null!");
335 }
336
337 String clsName = locator.getClass().getName();
338 String data = (locatorData != null) ? locatorData : "";
339 StringBuilder buf = new StringBuilder(BUF_SIZE + clsName.length()
340 + data.length());
341 buf.append(clsName);
342 buf.append('@').append(System.identityHashCode(locator));
343 buf.append("[ ").append(data).append(" ]");
344
345 return buf.toString();
346 }
347
348 /**
349 * Extracts the data of the string representation of a {@code Locator}.
350 * Strings created by the {@code locatorToString()} method contain some
351 * information specific to the {@code Locator} object involved,
352 * especially its identity hash code. This complicates things, for instance
353 * in unit tests, when locators that are equal should produce equal string
354 * representations. In such cases this method can be used. It produces a
355 * string containing only the class name (not fully qualified) and the data
356 * of the locator. The relevant parts are extracted from the given string,
357 * which must conform to the format produced by the
358 * {@code locatorToString()} method.
359 *
360 * @param locatorString the string for the locator (as generated by
361 * {@code locatorToString()})
362 * @return the data string extracted
363 * @throws IllegalArgumentException if the passed in string is <b>null</b>
364 * or does not conform to the expected format
365 * @see #locatorToString(Locator, String)
366 */
367 public static String locatorToDataString(String locatorString)
368 {
369 if (locatorString == null)
370 {
371 throw new IllegalArgumentException(
372 "Locator string must not be null!");
373 }
374
375 StringBuilder buf = new StringBuilder();
376 int posAt = find(locatorString, '@', 0);
377 int idx = posAt - 1;
378 while (idx > 0 && locatorString.charAt(idx) != '.')
379 {
380 idx--;
381 }
382 if (idx >= 0 && locatorString.charAt(idx) == '.')
383 {
384 idx++; // skip first dot
385 }
386
387 buf.append(locatorString.substring(idx, posAt));
388 buf.append(locatorString.substring(find(locatorString, '[', posAt)));
389
390 return buf.toString();
391 }
392
393 /**
394 * Returns the data string for the specified {@code Locator}. This is a
395 * short cut of {@code locatorToDataString(locator.toString()}. The
396 * {@code toString()} implementation of the locator must produce
397 * strings conforming to the format of {@code locatorToString()}.
398 *
399 * @param locator the locator to be transformed into a string
400 * @return the data string for this locator
401 * @throws IllegalArgumentException if the locator is <b>null</b> or has an
402 * invalid string representation
403 */
404 public static String locatorToDataString(Locator locator)
405 {
406 if (locator == null)
407 {
408 throw new IllegalArgumentException("Locator must not be null!");
409 }
410
411 return locatorToDataString(locator.toString());
412 }
413
414 /**
415 * Searches in the given string for the specified character starting at the
416 * start position. If the character cannot be found, an exception is thrown.
417 *
418 * @param s the string
419 * @param c the character to search for
420 * @param start the start index
421 * @return the position of the found character
422 * @throws IllegalArgumentException if the character cannot be found
423 */
424 private static int find(String s, char c, int start)
425 {
426 int pos = s.indexOf(c, start);
427 if (pos < 0)
428 {
429 throw new IllegalArgumentException("Cannot find '" + c + "' in "
430 + s);
431 }
432 return pos;
433 }
434 }