1 /***
2 * Copyright 2007 the original author or authors.
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 * For more information visit
11 * http://72miles.com and
12 * http://architecturerules.googlecode.com/svn/docs/index.html
13 */
14
15 package org.seventytwomiles.springframework.core.io;
16
17
18 import org.seventytwomiles.springframework.util.ClassUtils;
19 import org.seventytwomiles.springframework.util.ResourceUtils;
20
21 import java.io.File;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.net.URL;
26 import java.util.*;
27
28
29
30 /***
31 * <p>These classes are all extracted from the Spring Framework in order to
32 * remove the dependency on the Spring library.</p>
33 *
34 * @author mikenereson
35 * @noinspection SimplifiableIfStatement
36 */
37 public class ClassPathResource {
38
39
40 /***
41 * <p>symbol that separates folders</p>
42 *
43 * @parameter FOLDER_SEPARATOR String
44 */
45 private static final String FOLDER_SEPARATOR = "/";
46
47 /***
48 * <p>symbol that separates folders on the Windows platform</p>
49 *
50 * @parameter WINDOWS_FOLDER_SEPARATOR String
51 */
52 private static final String WINDOWS_FOLDER_SEPARATOR = "//";
53
54 /***
55 * <p>Parent directory path</p>
56 *
57 * @parameter TOP_PATH String
58 */
59 private static final String TOP_PATH = "..";
60
61 /***
62 * <p>Current path</p>
63 *
64 * @parameter CURRENT_PATH String
65 */
66 private static final String CURRENT_PATH = ".";
67
68 /***
69 * <p>the path that you are in</p>
70 *
71 * @parameter path String
72 */
73 private final String path;
74
75 /***
76 * <p>instance of ClassLoader</p>
77 *
78 * @parameter classLoader String
79 */
80 private ClassLoader classLoader;
81
82 /***
83 * <p>instance of Class</p>
84 *
85 * @parameter clazz String
86 */
87 private Class clazz;
88
89
90 /***
91 * <p>Create a new <code>ClassPathResource</code> for ClassLoader usage. A
92 * leading slash will be removed, as the ClassLoader resource access methods
93 * will not accept it. The thread context class loader will be used for
94 * loading the resource.</p>
95 *
96 * @param path the absolute path within the class path
97 * @see ClassLoader#getResourceAsStream(String)
98 */
99 public ClassPathResource(final String path) {
100 this(path, (ClassLoader) null);
101 }
102
103
104 /***
105 * <p>Create a new <code>ClassPathResource</code> for ClassLoader usage. A
106 * leading slash will be removed, as the ClassLoader resource access methods
107 * will not accept it.</p>
108 *
109 * @param path the absolute path within the classpath
110 * @param classLoader the class loader to load the resource with, or
111 * <code>null</code> for the thread context class loader
112 * @see ClassLoader#getResourceAsStream(String)
113 */
114 public ClassPathResource(String path, final ClassLoader classLoader) {
115
116 if (null == path || "".equals(path))
117 throw new IllegalArgumentException("path can not be empty or null");
118
119 if (path.startsWith("/"))
120 path = path.substring(1);
121
122 this.path = cleanPath(path);
123 this.classLoader
124 = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
125 }
126
127
128 /***
129 * <p>Create a new <code>ClassPathResource</code> for Class usage. The path
130 * can be relative to the given class, or absolute within the classpath via
131 * a leading slash.</p>
132 *
133 * @param path relative or absolute path within the class path
134 * @param clazz the class to load resources with
135 * @see Class#getResourceAsStream
136 */
137 public ClassPathResource(final String path, final Class clazz) {
138
139 if (null == path || "".equals(path))
140 throw new IllegalArgumentException("path can not be empty or null");
141
142 this.path = cleanPath(path);
143 this.clazz = clazz;
144 }
145
146
147 /***
148 * <p>Create a new <code>ClassPathResource</code> with optional ClassLoader
149 * and Class. Only for internal usage.</p>
150 *
151 * @param path relative or absolute path within the classpath
152 * @param classLoader the class loader to load the resource with, if any
153 * @param clazz the class to load resources with, if any
154 */
155 protected ClassPathResource(final String path,
156 final ClassLoader classLoader,
157 final Class clazz) {
158 this.path = path;
159 this.classLoader = classLoader;
160 this.clazz = clazz;
161 }
162
163
164 /***
165 * <p>This implementation returns a description that includes the class path
166 * location.</p>
167 *
168 * @return String description of File
169 */
170 private String getDescription() {
171 return "class path resource [" + this.path + "]";
172 }
173
174
175 /***
176 * <p>This implementation returns a File reference for the underlying class
177 * path resource, provided that it refers to a file in the file system.</p>
178 *
179 * @return File
180 * @throws IOException when file not found
181 * @noinspection RedundantThrows
182 */
183 public File getFile() throws IOException {
184 return ResourceUtils.getFile(getURL(), getDescription());
185 }
186
187
188 /***
189 * <p>This implementation returns a URL for the underlying class path
190 * resource.</p>
191 *
192 * @return URL
193 * @throws FileNotFoundException when file not found
194 * @see ClassLoader#getResource(String)
195 * @see Class#getResource(String)
196 */
197 private URL getURL() throws FileNotFoundException {
198
199 final URL url = clazz != null ? clazz.getResource(
200 path) : classLoader.getResource(path);
201
202 if (url == null)
203 throw new FileNotFoundException(
204 getDescription() + " cannot be resolved to URL because it does not exist");
205
206 return url;
207 }
208
209
210 /***
211 * <p>This implementation compares the underlying class path locations.</p>
212 */
213 @Override
214 public boolean equals(final Object object) {
215
216 if (object == this)
217 return true;
218
219 if (!(object instanceof ClassPathResource))
220 return false;
221
222 final ClassPathResource that = (ClassPathResource) object;
223
224 return (this.path.equals(that.path) &&
225 nullSafeEquals(this.classLoader, that.classLoader) &&
226 nullSafeEquals(this.clazz, that.clazz));
227 }
228
229
230 /***
231 * <p>Determine if the given objects are equal, returning <code>true</code>
232 * if both are <code>null</code> or <code>false</code> if only one is
233 * <code>null</code>. <p>Compares arrays with <code>Arrays.equals</code>,
234 * performing an equality check based on the array elements rather than the
235 * array reference.</p>
236 *
237 * @param object1 first Object to compare
238 * @param object2 second Object to compare
239 * @return whether the given objects are equal
240 * @see java.util.Arrays#equals
241 */
242 private boolean nullSafeEquals(final Object object1, final Object object2) {
243
244 if (object1 == object2)
245 return true;
246
247 if (object1 == null || object2 == null)
248 return false;
249
250 if (object1.equals(object2))
251 return true;
252
253 if (object1 instanceof Object[] && object2 instanceof Object[])
254 return Arrays.equals((Object[]) object1, (Object[]) object2);
255
256 if (object1 instanceof boolean[] && object2 instanceof boolean[])
257 return Arrays.equals((boolean[]) object1, (boolean[]) object2);
258
259 if (object1 instanceof byte[] && object2 instanceof byte[])
260 return Arrays.equals((byte[]) object1, (byte[]) object2);
261
262 if (object1 instanceof char[] && object2 instanceof char[])
263 return Arrays.equals((char[]) object1, (char[]) object2);
264
265 if (object1 instanceof double[] && object2 instanceof double[])
266 return Arrays.equals((double[]) object1, (double[]) object2);
267
268 if (object1 instanceof float[] && object2 instanceof float[])
269 return Arrays.equals((float[]) object1, (float[]) object2);
270
271 if (object1 instanceof int[] && object2 instanceof int[])
272 return Arrays.equals((int[]) object1, (int[]) object2);
273
274 if (object1 instanceof long[] && object2 instanceof long[])
275 return Arrays.equals((long[]) object1, (long[]) object2);
276
277 if (object1 instanceof short[] && object2 instanceof short[])
278 return Arrays.equals((short[]) object1, (short[]) object2);
279
280 return false;
281 }
282
283
284 /***
285 * <p>This implementation checks whether a File can be opened, falling back
286 * to whether an InputStream can be opened. This will cover both directories
287 * and content resources.</p>
288 *
289 * @return boolean
290 */
291 public boolean exists() {
292
293
294 try {
295
296 return getFile().exists();
297
298 } catch (final IOException ex) {
299
300
301 try {
302
303 final InputStream inputStream = getInputStream();
304 inputStream.close();
305 return true;
306
307 } catch (final Throwable throwable) {
308
309 return false;
310 }
311 }
312 }
313
314
315 /***
316 * <p>This implementation opens an InputStream for the given class path
317 * resource.</p>
318 *
319 * @return InputStream
320 * @throws FileNotFoundException when file not found
321 * @see ClassLoader#getResourceAsStream(String)
322 * @see Class#getResourceAsStream(String)
323 */
324 private InputStream getInputStream() throws FileNotFoundException {
325
326
327 final InputStream inputStream
328 = clazz != null ? clazz.getResourceAsStream(
329 path) : classLoader.getResourceAsStream(path);
330
331 if (inputStream == null)
332 throw new FileNotFoundException(
333 getDescription() + " cannot be opened because it does not exist");
334
335 return inputStream;
336 }
337
338
339 /***
340 * <p>Normalize the path by suppressing sequences like "path/.." and inner
341 * simple dots. <p>The result is convenient for path comparison. For other
342 * uses, notice that Windows separators ("\") are replaced by simple
343 * slashes.</p>
344 *
345 * @param path the original path
346 * @return the normalized path
347 */
348 private String cleanPath(final String path) {
349
350 String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR,
351 FOLDER_SEPARATOR);
352
353
354
355
356
357 final int prefixIndex = pathToUse.indexOf(":");
358 String prefix = "";
359
360 if (prefixIndex != -1) {
361
362 prefix = pathToUse.substring(0, prefixIndex + 1);
363 pathToUse = pathToUse.substring(prefixIndex + 1);
364 }
365
366 final String[] pathArray = delimitedListToStringArray(pathToUse,
367 FOLDER_SEPARATOR);
368
369 final List pathElements = new LinkedList();
370 int tops = 0;
371
372 for (int i = pathArray.length - 1; i >= 0; i--) {
373
374 if (CURRENT_PATH.equals(pathArray[i])) {
375
376
377
378 } else if (TOP_PATH.equals(pathArray[i])) {
379
380
381 tops++;
382
383 } else {
384
385 if (tops > 0) {
386
387
388 tops--;
389
390 } else {
391
392
393 pathElements.add(0, pathArray[i]);
394 }
395 }
396 }
397
398
399 for (int i = 0; i < tops; i++)
400 pathElements.add(0, TOP_PATH);
401
402 return prefix + collectionToDelimitedString(pathElements,
403 FOLDER_SEPARATOR);
404 }
405
406
407 /***
408 * <p>Replace all occurrences of a substring within a string with another
409 * string.</p>
410 *
411 * @param inString String to examine
412 * @param oldPattern String to replace
413 * @param newPattern String to insert
414 * @return a String with the replacements
415 */
416 private static String replace(final String inString,
417 final String oldPattern,
418 final String newPattern) {
419
420 if (inString == null)
421 return null;
422
423 if (oldPattern == null || newPattern == null)
424 return inString;
425
426 final StringBuffer stringBuffer = new StringBuffer();
427
428
429 int position = 0;
430 int index = inString.indexOf(oldPattern);
431
432
433 final int patternLength = oldPattern.length();
434
435 while (index >= 0) {
436
437 stringBuffer.append(inString.substring(position, index));
438 stringBuffer.append(newPattern);
439 position = index + patternLength;
440 index = inString.indexOf(oldPattern, position);
441 }
442
443 stringBuffer.append(inString.substring(position));
444
445
446 return stringBuffer.toString();
447 }
448
449
450 /***
451 * <p>Convenience method to return a Collection as a delimited (e.g. CSV)
452 * String. E.g. useful for <code>toString()</code> implementations.</p>
453 *
454 * @param collection the Collection to display
455 * @param deliminator the delimiter to use (probably a ",")
456 * @param prefix the String to start each element with
457 * @param suffix the String to end each element with
458 * @return String
459 */
460 private static String collectionToDelimitedString(
461 final Collection collection, final String deliminator,
462 final String prefix, final String suffix) {
463
464 if (collection.isEmpty())
465 return "";
466
467 final StringBuffer stringBuffer = new StringBuffer();
468 final Iterator iterator = collection.iterator();
469
470 while (iterator.hasNext()) {
471 stringBuffer.append(prefix).append(iterator.next()).append(suffix);
472
473 if (iterator.hasNext())
474 stringBuffer.append(deliminator);
475 }
476
477 return stringBuffer.toString();
478 }
479
480
481 /***
482 * <p>Convenience method to return a Collection as a delimited (e.g. CSV)
483 * String. E.g. useful for <code>toString()</code> implementations.</p>
484 *
485 * @param collection the Collection to display
486 * @param deliminator the delimiter to use (probably a ",")
487 * @return String
488 */
489 private String collectionToDelimitedString(final Collection collection,
490 final String deliminator) {
491 return collectionToDelimitedString(collection, deliminator, "", "");
492 }
493
494
495 /***
496 * Take a String which is a delimited list and convert it to a String array.
497 * <p>A single delimiter can consists of more than one character: It will
498 * still be considered as single delimiter string, rather than as bunch of
499 * potential delimiter characters - in contrast to <code>tokenizeToStringArray</code>.
500 *
501 * @param string the input String
502 * @param delimiter the delimiter between elements (this is a single
503 * delimiter, rather than a bunch individual delimiter characters)
504 * @return an array of the tokens in the list
505 */
506 private static String[] delimitedListToStringArray(final String string,
507 final String delimiter) {
508
509 if (string == null)
510 return new String[0];
511
512 if (delimiter == null)
513 return new String[]{string};
514
515 final List result = new ArrayList();
516
517 if ("".equals(delimiter)) {
518
519 for (int i = 0; i < string.length(); i++)
520 result.add(string.substring(i, i + 1));
521
522 } else {
523
524 int position = 0;
525 int deletePosition;
526
527 while ((deletePosition = string.indexOf(delimiter,
528 position)) != -1) {
529
530 result.add(string.substring(position, deletePosition));
531 position = deletePosition + delimiter.length();
532 }
533
534
535 if (string.length() > 0 && position <= string.length())
536 result.add(string.substring(position));
537 }
538
539 return toStringArray(result);
540 }
541
542
543 /***
544 * <p>Copy the given Collection into a String array. The Collection must
545 * contain String elements only.</p>
546 *
547 * @param collection the Collection to copy
548 * @return the String array (<code>null</code> if the passed-in Collection
549 * was <code>null</code>)
550 */
551 private static String[] toStringArray(final Collection collection) {
552
553 if (collection == null)
554 return null;
555
556 return (String[]) collection.toArray(new String[collection.size()]);
557 }
558
559 }