View Javadoc

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 }