001    /**
002     * Copyright 2010-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.common.util;
017    
018    import java.io.BufferedReader;
019    import java.io.BufferedWriter;
020    import java.io.File;
021    import java.io.FileWriter;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.InputStreamReader;
025    import java.io.OutputStream;
026    import java.io.OutputStreamWriter;
027    import java.io.PrintStream;
028    import java.io.Reader;
029    import java.io.StringReader;
030    import java.io.Writer;
031    import java.net.MalformedURLException;
032    import java.net.URI;
033    import java.net.URISyntaxException;
034    import java.net.URL;
035    import java.security.MessageDigest;
036    import java.security.NoSuchAlgorithmException;
037    import java.util.ArrayList;
038    import java.util.Arrays;
039    import java.util.Collections;
040    import java.util.List;
041    import java.util.Properties;
042    
043    import org.apache.commons.io.FileUtils;
044    import org.apache.commons.io.IOUtils;
045    import org.apache.commons.lang3.StringUtils;
046    import org.slf4j.Logger;
047    import org.slf4j.LoggerFactory;
048    import org.springframework.core.io.DefaultResourceLoader;
049    import org.springframework.core.io.Resource;
050    import org.springframework.core.io.ResourceLoader;
051    import org.springframework.util.Assert;
052    
053    public class LocationUtils {
054    
055            private static final Logger logger = LoggerFactory.getLogger(LocationUtils.class);
056    
057            private static final String FILE_PREFIX = "file:";
058            private static final String BACK_SLASH = "\\";
059            private static final String FORWARD_SLASH = "/";
060            private static final String SLASH_DOT_SLASH = "/./";
061            private static final String DOT_DOT_SLASH = "../";
062            private static final String SLASH_DOT_DOT = "/..";
063            private static final String CLASSPATH = "classpath:";
064            private static final String MD5 = "MD5";
065    
066            /**
067             * Get the MD5 checksum of the location
068             */
069            public static String getMD5Checksum(String location) {
070                    return getChecksum(location, MD5);
071            }
072    
073            /**
074             * Get the MD5 checksum of the file
075             */
076            public static String getMD5Checksum(File file) {
077                    return getChecksum(getCanonicalPath(file), MD5);
078            }
079    
080            /**
081             * Open a <code>PrintStream</code> to the indicated file. Parent directories are created if necessary.
082             */
083            public static final PrintStream openPrintStream(File file) throws IOException {
084                    return new PrintStream(FileUtils.openOutputStream(file));
085            }
086    
087            /**
088             * Open a <code>Writer</code> to the indicated file. Parent directories are created if necessary.
089             */
090            public static final Writer openWriter(File file) throws IOException {
091                    touch(file);
092                    return new FileWriter(file);
093            }
094    
095            /**
096             * 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.
097             */
098            public static final Writer openWriter(String location) throws IOException {
099                    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[] bytes = createChecksum(location, algorithm);
769                    return getChecksum(bytes, algorithm);
770            }
771    
772            public static String getChecksum(byte[] bytes, String algorithm) {
773                    StringBuilder sb = new StringBuilder();
774                    for (int i = 0; i < bytes.length; i++) {
775                            sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
776                    }
777                    return sb.toString();
778            }
779    
780            public static byte[] createChecksum(String location, String algorithm) {
781                    InputStream in = null;
782                    try {
783                            in = LocationUtils.getInputStream(location);
784                            return createChecksum(in, algorithm);
785                    } catch (IOException e) {
786                            throw new IllegalStateException("Unexpected IO error", e);
787                    } finally {
788                            IOUtils.closeQuietly(in);
789                    }
790            }
791    
792            public static byte[] createChecksum(InputStream in, String algorithm) throws IOException {
793                    try {
794                            byte[] buffer = new byte[1024];
795                            MessageDigest complete = MessageDigest.getInstance(algorithm);
796                            int numRead;
797                            do {
798                                    numRead = in.read(buffer);
799                                    if (numRead > 0) {
800                                            complete.update(buffer, 0, numRead);
801                                    }
802                            } while (numRead != -1);
803                            IOUtils.closeQuietly(in);
804                            return complete.digest();
805                    } catch (NoSuchAlgorithmException e) {
806                            throw new IllegalStateException("Unexpected message digest error", e);
807                    } catch (IOException e) {
808                            throw e;
809                    }
810            }
811    }