View Javadoc

1   /**
2    * Copyright 2010-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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 org.kuali.common.util;
17  
18  import java.io.BufferedReader;
19  import java.io.BufferedWriter;
20  import java.io.File;
21  import java.io.FileWriter;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.io.OutputStream;
26  import java.io.OutputStreamWriter;
27  import java.io.PrintStream;
28  import java.io.Reader;
29  import java.io.StringReader;
30  import java.io.Writer;
31  import java.net.MalformedURLException;
32  import java.net.URI;
33  import java.net.URISyntaxException;
34  import java.net.URL;
35  import java.security.MessageDigest;
36  import java.security.NoSuchAlgorithmException;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.Collections;
40  import java.util.List;
41  import java.util.Properties;
42  
43  import org.apache.commons.io.FileUtils;
44  import org.apache.commons.io.IOUtils;
45  import org.apache.commons.lang3.StringUtils;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  import org.springframework.core.io.DefaultResourceLoader;
49  import org.springframework.core.io.Resource;
50  import org.springframework.core.io.ResourceLoader;
51  import org.springframework.util.Assert;
52  
53  public class LocationUtils {
54  
55  	private static final Logger logger = LoggerFactory.getLogger(LocationUtils.class);
56  
57  	private static final String FILE_PREFIX = "file:";
58  	private static final String BACK_SLASH = "\\";
59  	private static final String FORWARD_SLASH = "/";
60  	private static final String SLASH_DOT_SLASH = "/./";
61  	private static final String DOT_DOT_SLASH = "../";
62  	private static final String SLASH_DOT_DOT = "/..";
63  	private static final String CLASSPATH = "classpath:";
64  	private static final String MD5 = "MD5";
65  
66  	/**
67  	 * Get the MD5 checksum of the location
68  	 */
69  	public static String getMD5Checksum(String location) {
70  		return getChecksum(location, MD5);
71  	}
72  
73  	/**
74  	 * Get the MD5 checksum of the file
75  	 */
76  	public static String getMD5Checksum(File file) {
77  		return getChecksum(getCanonicalPath(file), MD5);
78  	}
79  
80  	/**
81  	 * Open a <code>PrintStream</code> to the indicated file. Parent directories are created if necessary.
82  	 */
83  	public static final PrintStream openPrintStream(File file) throws IOException {
84  		return new PrintStream(FileUtils.openOutputStream(file));
85  	}
86  
87  	/**
88  	 * Open a <code>Writer</code> to the indicated file. Parent directories are created if necessary.
89  	 */
90  	public static final Writer openWriter(File file) throws IOException {
91  		touch(file);
92  		return new FileWriter(file);
93  	}
94  
95  	/**
96  	 * Open a <code>Writer</code> to the <code>location</code> (It must be a writable file on the local file system). Parent directories are created if necessary.
97  	 */
98  	public static final Writer openWriter(String location) throws IOException {
99  		return openWriter(new File(location));
100 	}
101 
102 	public static Properties getLocationProperties(LocationPropertiesContext context) {
103 
104 		Assert.notNull(context, "context is null");
105 
106 		Properties properties = context.getProperties();
107 		String keySuffix = context.getKeySuffix();
108 		String locationPropertiesSuffix = context.getLocationPropertiesSuffix();
109 		String encoding = context.getEncoding();
110 
111 		Assert.notNull(properties, "properties is null");
112 		Assert.notNull(keySuffix, "keySuffix is null");
113 		Assert.notNull(locationPropertiesSuffix, "locationPropertiesSuffix is null");
114 
115 		List<String> keys = PropertyUtils.getEndsWithKeys(properties, keySuffix);
116 
117 		Properties locationProperties = new Properties();
118 		for (String key : keys) {
119 			String location = properties.getProperty(key);
120 			if (!exists(location)) {
121 				continue;
122 			}
123 			String propertiesLocation = location + locationPropertiesSuffix;
124 			if (!exists(propertiesLocation)) {
125 				continue;
126 			}
127 			Properties p = PropertyUtils.load(propertiesLocation, encoding);
128 			locationProperties.putAll(p);
129 		}
130 		logger.info("Located {} properties for {} location listings", locationProperties.size(), keys.size());
131 		return locationProperties;
132 	}
133 
134 	public static TextMetaData getTextMetaData(File file) {
135 		return getTextMetaData(getCanonicalPath(file));
136 	}
137 
138 	public static TextMetaData getTextMetaData(String location) {
139 		long lines = 0;
140 		long size = 0;
141 		BufferedReader in = null;
142 		try {
143 			in = getBufferedReader(location);
144 			String s = in.readLine();
145 			while (s != null) {
146 				lines++;
147 				size += s.length();
148 				s = in.readLine();
149 			}
150 			return new TextMetaData(lines, size);
151 		} catch (IOException e) {
152 			throw new IllegalStateException(e);
153 		} finally {
154 			IOUtils.closeQuietly(in);
155 		}
156 	}
157 
158 	/**
159 	 * Count the lines of text in the file.
160 	 * 
161 	 * @param file
162 	 *            The file to read lines of text from
163 	 * @return A count representing the number of lines of text
164 	 * @throws IllegalStateException
165 	 *             If there is an i/o exception reading the file
166 	 * @see BufferedReader
167 	 */
168 	public static long getLineCount(File file) {
169 		return getLineCount(file, null);
170 	}
171 
172 	public static long getLineCount(File file, String encoding) {
173 		return getLineCount(getCanonicalPath(file));
174 	}
175 
176 	/**
177 	 * Count the lines of text in the location.
178 	 * 
179 	 * @param location
180 	 *            The location to read lines of text from
181 	 * @return A count representing the number of lines of text
182 	 * @throws IllegalStateException
183 	 *             If there is an i/o exception reading the file
184 	 * @see BufferedReader
185 	 * @deprecated Use getLineCount(location,encoding) instead
186 	 */
187 	@Deprecated
188 	public static long getLineCount(String location) {
189 		return getLineCount(location, null);
190 	}
191 
192 	/**
193 	 * Count the lines of text in the location.
194 	 * 
195 	 * @param location
196 	 *            The location to read lines of text from
197 	 * @return A count representing the number of lines of text
198 	 * @throws IllegalStateException
199 	 *             If there is an i/o exception reading the file
200 	 * @see BufferedReader
201 	 */
202 	public static long getLineCount(String location, String encoding) {
203 		long count = 0;
204 		BufferedReader in = null;
205 		try {
206 			in = getBufferedReader(location, encoding);
207 			while (in.readLine() != null) {
208 				count++;
209 			}
210 			return count;
211 		} catch (IOException e) {
212 			throw new IllegalStateException("Unexpected IO error", e);
213 		} finally {
214 			IOUtils.closeQuietly(in);
215 		}
216 	}
217 
218 	public static final void copyLocationsToFiles(List<String> locations, List<File> files) {
219 		Assert.isTrue(locations.size() == files.size());
220 		for (int i = 0; i < locations.size(); i++) {
221 			String location = locations.get(i);
222 			File destination = files.get(i);
223 			copyLocationToFile(location, destination);
224 		}
225 	}
226 
227 	/**
228 	 * Return the text that appears after <code>classpath:</code>. Throws <code>IllegalArgumentException</code> if location does not start with <code>classpath:</code>
229 	 */
230 	public static final String getClasspathFilename(String location) {
231 		return getClasspathFilenames(Arrays.asList(location)).get(0);
232 	}
233 
234 	/**
235 	 * Return the text that appears after <code>classpath:</code>. Throws <code>IllegalArgumentException</code> if any locations do not start with <code>classpath:</code>
236 	 */
237 	public static final List<String> getClasspathFilenames(List<String> locations) {
238 		List<String> classpathFilenames = new ArrayList<String>();
239 		for (String location : locations) {
240 			if (!isClasspathLocation(location)) {
241 				throw new IllegalArgumentException(location + " must start with " + CLASSPATH);
242 			} else {
243 				classpathFilenames.add(StringUtils.substring(location, CLASSPATH.length()));
244 			}
245 		}
246 		return classpathFilenames;
247 	}
248 
249 	/**
250 	 * Return <code>true</code> if location starts with <code>classpath:</code>
251 	 */
252 	public static final boolean isClasspathLocation(String location) {
253 		return StringUtils.startsWith(location, CLASSPATH);
254 	}
255 
256 	public static final List<String> getNormalizedPathFragments(String absolutePath, boolean directory) {
257 		String normalized = getNormalizedAbsolutePath(absolutePath);
258 		String[] tokens = StringUtils.split(normalized, FORWARD_SLASH);
259 		List<String> fragments = new ArrayList<String>();
260 		StringBuilder sb = new StringBuilder();
261 		sb.append(FORWARD_SLASH);
262 		int length = directory ? tokens.length : tokens.length - 1;
263 		for (int i = 0; i < length; i++) {
264 			if (i != 0) {
265 				sb.append(FORWARD_SLASH);
266 			}
267 			sb.append(tokens[i]);
268 			fragments.add(sb.toString());
269 		}
270 		return fragments;
271 	}
272 
273 	public static final List<String> getCanonicalPaths(List<File> files) {
274 		List<String> paths = new ArrayList<String>();
275 		for (File file : files) {
276 			String path = getCanonicalPath(file);
277 			paths.add(path);
278 		}
279 		return paths;
280 	}
281 
282 	public static final List<String> getLocations(String location, LocationType type, String encoding) {
283 		switch (type) {
284 		case LOCATION:
285 			return Collections.singletonList(location);
286 		case LOCATIONLIST:
287 			return getLocations(location, encoding);
288 		default:
289 			throw new IllegalArgumentException("Location type '" + type + "' is unknown");
290 		}
291 	}
292 
293 	public static final List<String> getLocations(String location, LocationType type) {
294 		return getLocations(location, type, null);
295 	}
296 
297 	public static final List<String> getLocations(String locationListing) {
298 		return getLocations(Collections.singletonList(locationListing), null);
299 	}
300 
301 	public static final List<String> getLocations(String locationListing, String encoding) {
302 		return getLocations(Collections.singletonList(locationListing), encoding);
303 	}
304 
305 	public static final List<String> getLocations(List<String> locationListings) {
306 		return getLocations(locationListings, null);
307 	}
308 
309 	public static final void copyLocationToFile(String location, File destination) {
310 		Assert.notNull(location);
311 		Assert.notNull(destination);
312 		logger.debug("Copying [{}]->[{}]", location, destination);
313 		InputStream in = null;
314 		try {
315 			in = getInputStream(location);
316 			FileUtils.copyInputStreamToFile(in, destination);
317 		} catch (IOException e) {
318 			throw new IllegalStateException(e);
319 		} finally {
320 			IOUtils.closeQuietly(in);
321 		}
322 	}
323 
324 	public static final List<File> getFiles(File dir, List<String> filenames) {
325 		List<File> files = new ArrayList<File>();
326 		for (String filename : filenames) {
327 			File file = new File(dir, filename);
328 			files.add(file);
329 		}
330 		return files;
331 	}
332 
333 	public static final List<String> getFilenames(List<String> locations) {
334 		Assert.notNull(locations);
335 		List<String> filenames = new ArrayList<String>();
336 		for (String location : locations) {
337 			filenames.add(getFilename(location));
338 		}
339 		return filenames;
340 	}
341 
342 	/**
343 	 * Throw IllegalArgumentException if the locationListing does not exist, or if any of the locations inside the locationListing do not exist
344 	 */
345 	public static final void validateLocationListing(String locationListing) {
346 		validateLocationListings(Collections.singletonList(locationListing));
347 	}
348 
349 	/**
350 	 * Throw IllegalArgumentException if any of the locationListings do not exist, or if any of the locations inside any of the locationListings do not exist
351 	 */
352 	public static final void validateLocationListings(List<String> locationListings) {
353 		for (String locationListing : locationListings) {
354 			validateLocation(locationListing);
355 			List<String> locations = getLocations(locationListing);
356 			validateLocations(locations);
357 		}
358 	}
359 
360 	public static final void validateLocations(List<String> locations) {
361 		for (String location : locations) {
362 			validateLocation(location);
363 		}
364 	}
365 
366 	/**
367 	 * Throw IllegalArgumentException if the location does not exist
368 	 */
369 	public static final void validateLocation(String location) {
370 		validateLocation(location, "[" + location + "] does not exist");
371 	}
372 
373 	/**
374 	 * Throw IllegalArgumentException if the location does not exist
375 	 */
376 	public static final void validateLocation(String location, String message) {
377 		Assert.isTrue(exists(location), message);
378 	}
379 
380 	public static final List<String> getLocations(List<String> locationListings, String encoding) {
381 		List<String> locations = new ArrayList<String>();
382 		for (String locationListing : locationListings) {
383 			List<String> lines = readLines(locationListing, encoding);
384 			locations.addAll(lines);
385 		}
386 		return locations;
387 	}
388 
389 	public static final String getCanonicalURLString(File file) {
390 		if (file == null) {
391 			return null;
392 		}
393 		String path = getCanonicalPath(file);
394 		File canonical = new File(path);
395 		return getURLString(canonical);
396 	}
397 
398 	public static final void validateNormalizedPath(String originalPath, String normalizedPath) {
399 		if (CollectionUtils.containsAny(normalizedPath, Arrays.asList(SLASH_DOT_DOT, SLASH_DOT_SLASH, DOT_DOT_SLASH))) {
400 			throw new IllegalArgumentException("[" + originalPath + "] could not be normalized. Normalized path [" + normalizedPath + "]");
401 		}
402 	}
403 
404 	/**
405 	 * Resolve and remove <code>..</code> and <code>.</code> from <code>absolutePath</code> after converting any back slashes to forward slashes
406 	 */
407 	public static final String getNormalizedAbsolutePath(String absolutePath) {
408 		if (absolutePath == null) {
409 			return null;
410 		}
411 		String replaced = StringUtils.replace(absolutePath, BACK_SLASH, FORWARD_SLASH);
412 		boolean absolute = StringUtils.startsWith(replaced, FORWARD_SLASH);
413 		if (!absolute) {
414 			throw new IllegalArgumentException("[" + absolutePath + "] is not an absolute path.");
415 		}
416 		String prefixed = FILE_PREFIX + replaced;
417 		try {
418 			URI rawURI = new URI(prefixed);
419 			URI normalizedURI = rawURI.normalize();
420 			URL normalizedURL = normalizedURI.toURL();
421 			String externalForm = normalizedURL.toExternalForm();
422 			String trimmed = StringUtils.substring(externalForm, FILE_PREFIX.length());
423 			validateNormalizedPath(absolutePath, trimmed);
424 			return trimmed;
425 		} catch (MalformedURLException e) {
426 			throw new IllegalArgumentException(e);
427 		} catch (URISyntaxException e) {
428 			throw new IllegalArgumentException(e);
429 		}
430 	}
431 
432 	public static final String getURLString(File file) {
433 		if (file == null) {
434 			return null;
435 		}
436 		try {
437 			URI uri = file.toURI();
438 			URL url = uri.toURL();
439 			return url.toExternalForm();
440 		} catch (MalformedURLException e) {
441 			throw new IllegalArgumentException(e);
442 		}
443 	}
444 
445 	public static final void forceMkdir(File file) {
446 		try {
447 			FileUtils.forceMkdir(file);
448 		} catch (IOException e) {
449 			throw new IllegalArgumentException("Unexpected IO error", e);
450 		}
451 	}
452 
453 	public static final void touch(File file) {
454 		try {
455 			FileUtils.touch(file);
456 		} catch (IOException e) {
457 			throw new IllegalArgumentException("Unexpected IO error", e);
458 		}
459 	}
460 
461 	public static final String getCanonicalPath(File file) {
462 		try {
463 			return file.getCanonicalPath();
464 		} catch (IOException e) {
465 			throw new IllegalArgumentException("Unexpected IO error", e);
466 		}
467 	}
468 
469 	/**
470 	 * Null safe method to unconditionally attempt to delete <code>filename</code> without throwing an exception. If <code>filename</code> is a directory, delete it and all
471 	 * sub-directories.
472 	 */
473 	public static final boolean deleteFileQuietly(String filename) {
474 		File file = getFileQuietly(filename);
475 		return FileUtils.deleteQuietly(file);
476 	}
477 
478 	/**
479 	 * Null safe method for getting a <code>File</code> handle from <code>filename</code>. If <code>filename</code> is null, null is returned.
480 	 */
481 	public static final File getFileQuietly(String filename) {
482 		if (filename == null) {
483 			return null;
484 		} else {
485 			return new File(filename);
486 		}
487 	}
488 
489 	/**
490 	 * Get the contents of <code>file</code> as a <code>String</code> using the platform's default character encoding.
491 	 */
492 	public static final String toString(File file) {
493 		return toString(file, null);
494 	}
495 
496 	/**
497 	 * Get the contents of <code>file</code> as a <code>String</code> using the specified character encoding.
498 	 */
499 	public static final String toString(File file, String encoding) {
500 		return toString(getCanonicalPath(file), encoding);
501 	}
502 
503 	/**
504 	 * Get the contents of <code>location</code> as a <code>String</code> using the platform's default character encoding.
505 	 */
506 	public static final String toString(String location) {
507 		return toString(location, null);
508 	}
509 
510 	/**
511 	 * Get the contents of <code>location</code> as a <code>String</code> using the specified character encoding.
512 	 */
513 	public static final String toString(String location, String encoding) {
514 		InputStream in = null;
515 		try {
516 			in = getInputStream(location);
517 			if (encoding == null) {
518 				return IOUtils.toString(in);
519 			} else {
520 				return IOUtils.toString(in, encoding);
521 			}
522 		} catch (IOException e) {
523 			throw new IllegalStateException("Unexpected IO error", e);
524 		} finally {
525 			IOUtils.closeQuietly(in);
526 		}
527 	}
528 
529 	/**
530 	 * Get the contents of <code>s</code> as a list of <code>String's</code> one entry per line
531 	 */
532 	public static final List<String> readLinesFromString(String s) {
533 		Reader reader = getBufferedReaderFromString(s);
534 		return readLinesAndClose(reader);
535 	}
536 
537 	public static final List<String> readLinesAndClose(InputStream in) {
538 		return readLinesAndClose(in, null);
539 	}
540 
541 	public static final List<String> readLinesAndClose(InputStream in, String encoding) {
542 		Reader reader = null;
543 		try {
544 			reader = getBufferedReader(in, encoding);
545 			return readLinesAndClose(reader);
546 		} catch (IOException e) {
547 			throw new IllegalStateException("Unexpected IO error", e);
548 		} finally {
549 			IOUtils.closeQuietly(reader);
550 		}
551 	}
552 
553 	public static final List<String> readLinesAndClose(Reader reader) {
554 		try {
555 			return IOUtils.readLines(reader);
556 		} catch (IOException e) {
557 			throw new IllegalStateException("Unexpected IO error", e);
558 		} finally {
559 			IOUtils.closeQuietly(reader);
560 		}
561 	}
562 
563 	/**
564 	 * Get the contents of <code>file</code> as a list of <code>String's</code> one entry per line using the platform default encoding
565 	 */
566 	public static final List<String> readLines(File file) {
567 		return readLines(getCanonicalPath(file));
568 	}
569 
570 	/**
571 	 * Get the contents of <code>location</code> as a list of <code>String's</code> one entry per line using the platform default encoding
572 	 */
573 	public static final List<String> readLines(String location) {
574 		return readLines(location, null);
575 	}
576 
577 	/**
578 	 * Get the contents of <code>location</code> as a list of <code>String's</code> one entry per line using the encoding indicated.
579 	 */
580 	public static final List<String> readLines(String location, String encoding) {
581 		Reader reader = null;
582 		try {
583 			reader = getBufferedReader(location, encoding);
584 			return readLinesAndClose(reader);
585 		} catch (IOException e) {
586 			throw new IllegalStateException("Unexpected IO error", e);
587 		} finally {
588 			IOUtils.closeQuietly(reader);
589 		}
590 	}
591 
592 	/**
593 	 * Return a <code>BufferedReader</code> for the location indicated using the platform default encoding.
594 	 */
595 	public static final BufferedReader getBufferedReader(String location) throws IOException {
596 		return getBufferedReader(location, null);
597 	}
598 
599 	/**
600 	 * Return a <code>BufferedReader</code> for the location indicated using the encoding indicated.
601 	 */
602 	public static final BufferedReader getBufferedReader(String location, String encoding) throws IOException {
603 		try {
604 			InputStream in = getInputStream(location);
605 			return getBufferedReader(in, encoding);
606 		} catch (IOException e) {
607 			throw new IOException("Unexpected IO error", e);
608 		}
609 	}
610 
611 	/**
612 	 * Return a <code>BufferedReader</code> that reads from <code>s</code>
613 	 */
614 	public static final BufferedReader getBufferedReaderFromString(String s) {
615 		return new BufferedReader(new StringReader(s));
616 	}
617 
618 	/**
619 	 * Return a <code>Writer</code> that writes to <code>out</code> using the indicated encoding. <code>null</code> means use the platform's default encoding.
620 	 */
621 	public static final Writer getWriter(OutputStream out, String encoding) throws IOException {
622 		if (encoding == null) {
623 			return new BufferedWriter(new OutputStreamWriter(out));
624 		} else {
625 			return new BufferedWriter(new OutputStreamWriter(out, encoding));
626 		}
627 	}
628 
629 	/**
630 	 * Return a <code>BufferedReader</code> that reads from <code>file</code> using the indicated encoding. <code>null</code> means use the platform's default encoding.
631 	 */
632 	public static final BufferedReader getBufferedReader(File file, String encoding) throws IOException {
633 		return getBufferedReader(FileUtils.openInputStream(file), encoding);
634 	}
635 
636 	/**
637 	 * Return a <code>BufferedReader</code> that reads from <code>in</code> using the indicated encoding. <code>null</code> means use the platform's default encoding.
638 	 */
639 	public static final BufferedReader getBufferedReader(InputStream in, String encoding) throws IOException {
640 		if (encoding == null) {
641 			return new BufferedReader(new InputStreamReader(in));
642 		} else {
643 			return new BufferedReader(new InputStreamReader(in, encoding));
644 		}
645 	}
646 
647 	/**
648 	 * Null safe method for determining if <code>location</code> is an existing file.
649 	 */
650 	public static final boolean isExistingFile(String location) {
651 		if (location == null) {
652 			return false;
653 		}
654 		File file = new File(location);
655 		return file.exists();
656 	}
657 
658 	/**
659 	 * Null safe method for determining if <code>location</code> exists.
660 	 */
661 	public static final boolean exists(File file) {
662 		if (file == null) {
663 			return false;
664 		}
665 		String location = getCanonicalPath(file);
666 		if (isExistingFile(location)) {
667 			return true;
668 		} else {
669 			Resource resource = getResource(location);
670 			return resource.exists();
671 		}
672 	}
673 
674 	public static void validateExists(List<String> locations) {
675 		StringBuilder sb = new StringBuilder();
676 		for (String location : locations) {
677 			if (!LocationUtils.exists(location)) {
678 				sb.append("Location [" + location + "] does not exist\n");
679 			}
680 		}
681 		if (sb.length() > 0) {
682 			throw new IllegalArgumentException(sb.toString());
683 		}
684 	}
685 
686 	/**
687 	 * Null safe method for determining if <code>location</code> exists.
688 	 */
689 	public static final boolean exists(String location) {
690 		if (location == null) {
691 			return false;
692 		}
693 		if (isExistingFile(location)) {
694 			return true;
695 		} else {
696 			Resource resource = getResource(location);
697 			return resource.exists();
698 		}
699 	}
700 
701 	/**
702 	 * Open an <code>InputStream</code> to <code>location</code>. If <code>location</code> is the path to an existing <code>File</code> on the local file system, a
703 	 * <code>FileInputStream</code> is returned. Otherwise Spring's resource loading framework is used to open an <code>InputStream</code> to <code>location</code>.
704 	 */
705 	public static final InputStream getInputStream(String location) throws IOException {
706 		if (isExistingFile(location)) {
707 			return FileUtils.openInputStream(new File(location));
708 		}
709 		Resource resource = getResource(location);
710 		return resource.getInputStream();
711 	}
712 
713 	public static final Resource getResource(String location) {
714 		if (location == null) {
715 			return null;
716 		}
717 		ResourceLoader loader = new DefaultResourceLoader();
718 		return loader.getResource(location);
719 	}
720 
721 	public static final String getFilename(String location) {
722 		if (location == null) {
723 			return null;
724 		}
725 		if (isExistingFile(location)) {
726 			return getFileQuietly(location).getName();
727 		} else {
728 			Resource resource = getResource(location);
729 			return resource.getFilename();
730 		}
731 	}
732 
733 	public static final List<String> getAbsolutePaths(List<File> files) {
734 		List<String> results = new ArrayList<String>(files.size());
735 
736 		for (File f : files) {
737 			results.add(f.getAbsolutePath());
738 		}
739 
740 		return results;
741 	}
742 
743 	public static final ComparisonResults getLocationListComparison(List<String> newLocations, List<String> originalLocations) {
744 		ComparisonResults result = new ComparisonResults();
745 
746 		result.setAdded(new ArrayList<String>());
747 		result.setSame(new ArrayList<String>());
748 		result.setDeleted(new ArrayList<String>());
749 
750 		for (String newLocation : newLocations) {
751 			if (originalLocations.contains(newLocation)) {
752 				// if a location is in both lists, add it to the "same" list
753 				result.getSame().add(newLocation);
754 			} else {
755 				// if a location is only in the new list, add it to the "added" list
756 				result.getAdded().add(newLocation);
757 			}
758 		}
759 
760 		// the "deleted" list will contain all locations from the original list that are NOT in the new list
761 		result.getDeleted().addAll(originalLocations);
762 		result.getDeleted().removeAll(newLocations);
763 
764 		return result;
765 	}
766 
767 	public static String getChecksum(String location, String algorithm) {
768 		byte[] b = createChecksum(location, algorithm);
769 		StringBuilder sb = new StringBuilder();
770 		for (int i = 0; i < b.length; i++) {
771 			sb.append(Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1));
772 		}
773 		return sb.toString();
774 	}
775 
776 	public static byte[] createChecksum(String location, String algorithm) {
777 
778 		InputStream in = null;
779 		try {
780 
781 			// Open an input stream
782 			in = LocationUtils.getInputStream(location);
783 
784 			byte[] buffer = new byte[1024];
785 			MessageDigest complete = MessageDigest.getInstance(algorithm);
786 			int numRead;
787 			do {
788 				numRead = in.read(buffer);
789 				if (numRead > 0) {
790 					complete.update(buffer, 0, numRead);
791 				}
792 			} while (numRead != -1);
793 			IOUtils.closeQuietly(in);
794 			return complete.digest();
795 		} catch (NoSuchAlgorithmException e) {
796 			throw new IllegalStateException("Unexpected message digest error", e);
797 		} catch (IOException e) {
798 			throw new IllegalStateException("Unexpected IO error", e);
799 		} finally {
800 			IOUtils.closeQuietly(in);
801 		}
802 	}
803 
804 }