View Javadoc
1   package org.andromda.core.common;
2   
3   import java.io.File;
4   import java.io.FileNotFoundException;
5   import java.io.IOException;
6   import java.io.InputStream;
7   import java.io.Reader;
8   import java.net.MalformedURLException;
9   import java.net.URL;
10  import java.net.URLConnection;
11  import java.net.URLDecoder;
12  import java.util.ArrayList;
13  import java.util.Collection;
14  import java.util.Collections;
15  import java.util.Enumeration;
16  import java.util.List;
17  import java.util.ListIterator;
18  import java.util.zip.ZipEntry;
19  import java.util.zip.ZipFile;
20  
21  import org.apache.commons.io.FileUtils;
22  import org.apache.commons.io.IOUtils;
23  import org.apache.commons.io.filefilter.TrueFileFilter;
24  import org.apache.commons.lang.StringUtils;
25  import org.apache.log4j.Logger;
26  
27  /**
28   * Provides utilities for loading resources.
29   *
30   * @author Chad Brandon
31   * @author Bob Fields
32   * @author Michail Plushnikov
33   */
34  public class ResourceUtils
35  {
36      private static final Logger logger = Logger.getLogger(ResourceUtils.class);
37  
38      /**
39       * All archive files start with this prefix.
40       */
41      private static final String ARCHIVE_PREFIX = "jar:";
42  
43      /**
44       * The prefix for URL file resources.
45       */
46      private static final String FILE_PREFIX = "file:";
47  
48      /**
49       * The prefix to use for searching the classpath for a resource.
50       */
51      private static final String CLASSPATH_PREFIX = "classpath:";
52  
53      /**
54       * Retrieves a resource from the current classpath.
55       *
56       * @param resourceName the name of the resource
57       * @return the resource url
58       */
59      public static URL getResource(final String resourceName)
60      {
61          ExceptionUtils.checkEmpty(
62              "resourceName",
63              resourceName);
64          final ClassLoader loader = ClassUtils.getClassLoader();
65          URL resource = loader != null ? loader.getResource(resourceName) : null;
66          //if (resource==null)
67          return resource;
68      }
69  
70      /**
71       * Loads the resource and returns the contents as a String.
72       *
73       * @param resource the name of the resource.
74       * @return String
75       */
76      public static String getContents(final URL resource)
77      {
78          String result = null;
79  
80          InputStream in = null;
81          try
82          {
83              if(null!=resource)
84              {
85                  in = resource.openStream();
86                  result = IOUtils.toString(in);
87              }
88          }
89          catch (final IOException ex) {
90              throw new RuntimeException(ex);
91          }finally {
92              IOUtils.closeQuietly(in);
93          }
94          return result;
95      }
96  
97      /**
98       * Loads the resource and returns the contents as a String.
99       *
100      * @param resource the name of the resource.
101      * @return the contents of the resource as a string.
102      */
103     public static String getContents(final Reader resource)
104     {
105         String result;
106         try {
107             result = IOUtils.toString(resource);
108         }catch (IOException ex) {
109             throw new RuntimeException(ex);
110         }finally {
111             IOUtils.closeQuietly(resource);
112         }
113         return StringUtils.trimToEmpty(result);
114     }
115 
116     /**
117      * If the <code>resource</code> represents a classpath archive (i.e. jar, zip, etc), this method will retrieve all
118      * contents from that resource as a List of relative paths (relative to the archive base). Otherwise an empty List
119      * will be returned.
120      *
121      * @param resource the resource from which to retrieve the contents
122      * @return a list of Strings containing the names of every nested resource found in this resource.
123      */
124     public static List<String> getClassPathArchiveContents(final URL resource)
125     {
126         final List<String> contents = new ArrayList<String>();
127         if (isArchive(resource))
128         {
129             final ZipFile archive = getArchive(resource);
130             if (archive != null)
131             {
132                 for (final Enumeration<? extends ZipEntry> entries = archive.entries(); entries.hasMoreElements();)
133                 {
134                     final ZipEntry entry = entries.nextElement();
135                     contents.add(entry.getName());
136                 }
137                 try
138                 {
139                     archive.close();
140                 }
141                 catch (IOException ex)
142                 {
143                     // Ignore
144                 }
145             }
146         }
147         return contents;
148     }
149 
150     /**
151      * If this <code>resource</code> happens to be a directory, it will load the contents of that directory into a
152      * List and return the list of names relative to the given <code>resource</code> (otherwise it will return an empty
153      * List).
154      *
155      * @param resource the resource from which to retrieve the contents
156      * @param levels the number of levels to step down if the resource ends up being a directory (if its an artifact,
157      *               levels will be ignored).
158      * @return a list of Strings containing the names of every nested resource found in this resource.
159      */
160     public static List<String> getDirectoryContents(
161         final URL resource,
162         final int levels)
163     {
164         return getDirectoryContents(resource, levels, true);
165     }
166 
167     /**
168      * The character used for substituting whitespace in paths.
169      */
170     private static final String PATH_WHITESPACE_CHARACTER = "%20";
171 
172     /**
173      * Replaces any escape characters in the given file path with their
174      * counterparts.
175      *
176      * @param filePath the path of the file to unescape.
177      * @return the unescaped path.
178      */
179     public static String unescapeFilePath(String filePath)
180     {
181         if (StringUtils.isNotBlank(filePath))
182         {
183             filePath = filePath.replaceAll(PATH_WHITESPACE_CHARACTER, " ");
184         }
185         return filePath;
186     }
187 
188     /**
189      * If this <code>resource</code> happens to be a directory, it will load the contents of that directory into a
190      * List and return the list of names relative to the given <code>resource</code> (otherwise it will return an empty
191      * List).
192      *
193      * @param resource the resource from which to retrieve the contents
194      * @param levels the number of levels to step down if the resource ends up being a directory (if its an artifact,
195      *               levels will be ignored).
196      * @param includeSubdirectories whether or not to include subdirectories in the contents.
197      * @return a list of Strings containing the names of every nested resource found in this resource.
198      */
199     public static List<String> getDirectoryContents(
200         final URL resource,
201         final int levels,
202         boolean includeSubdirectories)
203     {
204         final List<String> contents = new ArrayList<String>();
205         if (resource != null)
206         {
207             // - create the file and make sure we remove any path white space characters
208             final File fileResource = new File(unescapeFilePath(resource.getFile()));
209             if (fileResource.isDirectory())
210             {
211                 File rootDirectory = fileResource;
212                 for (int ctr = 0; ctr < levels; ctr++)
213                 {
214                     rootDirectory = rootDirectory.getParentFile();
215                 }
216                 final File pluginDirectory = rootDirectory;
217                 loadFiles(
218                     pluginDirectory,
219                     contents,
220                     includeSubdirectories);
221 
222                 // - remove the root path from each file
223                 for (final ListIterator<String> iterator = contents.listIterator(); iterator.hasNext();)
224                 {
225                     final String filePath = iterator.next();
226                     iterator.set(
227                         StringUtils.replace(
228                             filePath.replace(
229                                 '\\',
230                                 '/'),
231                             pluginDirectory.getPath().replace(
232                                 '\\',
233                                 '/') + '/',
234                             ""));
235                 }
236             }
237         }
238         return contents;
239     }
240 
241     /**
242      * Loads all files find in the <code>directory</code> and adds them to the <code>fileList</code>.
243      *
244      * @param directory the directory from which to load all files.
245      * @param fileList  the Collection of files to which we'll add the found files.
246      * @param includeSubdirectories whether or not to include sub directories when loading the files.
247      */
248     private static void loadFiles(
249         final File directory,
250         final Collection<String> fileList,
251         boolean includeSubdirectories)
252     {
253         final Collection<File> lAllFiles = loadFiles(directory, includeSubdirectories);
254         for (File file : lAllFiles)
255         {
256             fileList.add(file.getPath());
257         }
258     }
259 
260     /**
261      * Loads all files find in the <code>directory</code> and returns them as Collection
262      *
263      * @param directory the directory from which to load all files.
264      * @param includeSubdirectories whether or not to include sub directories when loading the files.
265      * @return Collection with all files found in the directory
266      */
267     private static Collection<File> loadFiles(
268         final File directory,
269         boolean includeSubdirectories)
270     {
271         Collection<File> result = Collections.emptyList();
272         if(null!=directory && directory.exists())
273         {
274             result = FileUtils.listFiles(
275                     directory.isDirectory()? directory : directory.getParentFile(),
276                     TrueFileFilter.INSTANCE,
277                     includeSubdirectories ? TrueFileFilter.INSTANCE : null);
278         }
279         return result;
280     }
281 
282     /**
283      * Returns true/false on whether or not this <code>resource</code> represents an archive or not (i.e. jar, or zip,
284      * etc).
285      *
286      * @param resource
287      * @return true if its an archive, false otherwise.
288      */
289     public static boolean isArchive(final URL resource)
290     {
291         return resource != null && resource.toString().startsWith(ARCHIVE_PREFIX);
292     }
293 
294     private static final String URL_DECODE_ENCODING = "UTF-8";
295 
296     /**
297      * If this <code>resource</code> is an archive file, it will return the resource as an archive.
298      *
299      * @param resource
300      * @return the archive as a ZipFile
301      */
302     public static ZipFile getArchive(final URL resource)
303     {
304         try
305         {
306             ZipFile archive = null;
307             if (resource != null)
308             {
309                 String resourceUrl = resource.toString();
310                 resourceUrl = resourceUrl.replaceFirst(
311                         ARCHIVE_PREFIX,
312                         "");
313                 final int entryPrefixIndex = resourceUrl.indexOf('!');
314                 if (entryPrefixIndex != -1)
315                 {
316                     resourceUrl = resourceUrl.substring(
317                             0,
318                             entryPrefixIndex);
319                 }
320                 resourceUrl = URLDecoder.decode(new URL(resourceUrl).getFile(), URL_DECODE_ENCODING);
321                 File zipFile = new File(resourceUrl);
322                 if (zipFile.exists())
323                 {
324                     archive = new ZipFile(resourceUrl);
325                 }
326                 else
327                 {
328                     // ZipFile doesn't give enough detail about missing file
329                     throw new FileNotFoundException("ResourceUtils.getArchive " + resourceUrl + " NOT FOUND.");
330                 }
331             }
332             return archive;
333         }
334         // Don't unnecessarily wrap RuntimeException
335         catch (final RuntimeException ex)
336         {
337             throw ex;
338         }
339         // But don't require Exception declaration either.
340         catch (final Throwable throwable)
341         {
342             throw new RuntimeException(throwable);
343         }
344     }
345 
346     /**
347      * Loads the file resource and returns the contents as a String.
348      *
349      * @param resourceName the name of the resource.
350      * @return String
351      */
352     public static String getContents(final String resourceName)
353     {
354         return getContents(getResource(resourceName));
355     }
356 
357     /**
358      * Takes a className as an argument and returns the URL for the class.
359      *
360      * @param className name of class
361      * @return java.net.URL
362      */
363     public static URL getClassResource(final String className)
364     {
365         ExceptionUtils.checkEmpty(
366             "className",
367             className);
368         return getResource(getClassNameAsResource(className));
369     }
370 
371     /**
372      * Gets the class name as a resource.
373      *
374      * @param className the name of the class.
375      * @return the class name as a resource path.
376      */
377     private static String getClassNameAsResource(final String className)
378     {
379         return className.replace('.','/') + ".class";
380     }
381 
382     /**
383      * <p/>
384      * Retrieves a resource from an optionally given <code>directory</code> or from the package on the classpath. </p>
385      * <p/>
386      * If the directory is specified and is a valid directory then an attempt at finding the resource by appending the
387      * <code>resourceName</code> to the given <code>directory</code> will be made, otherwise an attempt to find the
388      * <code>resourceName</code> directly on the classpath will be initiated. </p>
389      *
390      * @param resourceName the name of a resource
391      * @param directory the directory location
392      * @return the resource url
393      */
394     public static URL getResource(
395         final String resourceName,
396         final String directory)
397     {
398         ExceptionUtils.checkEmpty(
399             "resourceName",
400             resourceName);
401 
402         if (directory != null)
403         {
404             final File file = new File(directory, resourceName);
405             if (file.exists())
406             {
407                 try
408                 {
409                     return file.toURI().toURL();
410                 }
411                 catch (final MalformedURLException exception)
412                 {
413                     // - ignore, we just try to find the resource on the classpath
414                 }
415             }
416         }
417         return getResource(resourceName);
418     }
419 
420     /**
421      * Makes the directory for the given location if it doesn't exist.
422      *
423      * @param location the location to make the directory.
424      */
425     public static void makeDirectories(final String location)
426     {
427         final File file = new File(location);
428         makeDirectories(file);
429     }
430 
431     /**
432      * Makes the directory for the given location if it doesn't exist.
433      *
434      * @param location the location to make the directory.
435      */
436     public static void makeDirectories(final File location)
437     {
438         final File parent = location.getParentFile();
439         if (parent != null)
440         {
441             parent.mkdirs();
442         }
443     }
444 
445     /**
446      * Gets the time as a <code>long</code> when this <code>resource</code> was last modified.
447      * If it can not be determined <code>0</code> is returned.
448      *
449      * @param resource the resource from which to retrieve
450      *        the last modified time.
451      * @return the last modified time or 0 if it couldn't be retrieved.
452      */
453     public static long getLastModifiedTime(final URL resource)
454     {
455         long lastModified;
456         try
457         {
458             final File file = new File(resource.getFile());
459             if (file.exists())
460             {
461                 lastModified = file.lastModified();
462             }
463             else
464             {
465                 URLConnection uriConnection = resource.openConnection();
466                 lastModified = uriConnection.getLastModified();
467 
468                 // - we need to set the urlConnection to null and explicitly
469                 //   call garbage collection, otherwise the JVM won't let go
470                 //   of the URL resource
471 //                uriConnection = null;
472 //                System.gc();
473                 IOUtils.closeQuietly(uriConnection.getInputStream());
474             }
475         }
476         catch (final Exception exception)
477         {
478             lastModified = 0;
479         }
480         return lastModified;
481     }
482 
483     /**
484      * <p>
485      * Retrieves a resource from an optionally given <code>directory</code> or from the package on the classpath.
486      * </p>
487      * If the directory is specified and is a valid directory then an attempt at finding the resource by appending the
488      * <code>resourceName</code> to the given <code>directory</code> will be made, otherwise an attempt to find the
489      * <code>resourceName</code> directly on the classpath will be initiated. </p>
490      *
491      * @param resourceName the name of a resource
492      * @param directory the directory location
493      * @return the resource url
494      */
495     public static URL getResource(
496         final String resourceName,
497         final URL directory)
498     {
499         String directoryLocation = null;
500         if (directory != null)
501         {
502             directoryLocation = directory.getFile();
503         }
504         return getResource(
505             resourceName,
506             directoryLocation);
507     }
508 
509     /**
510      * Attempts to construct the given <code>path</code>
511      * to a URL instance. If the argument cannot be resolved as a resource
512      * on the file system this method will attempt to locate it on the
513      * classpath.
514      *
515      * @param path the path from which to construct the URL.
516      * @return the constructed URL or null if one couldn't be constructed.
517      */
518     public static URL toURL(String path)
519     {
520         URL url = null;
521         if (path != null)
522         {
523             path = ResourceUtils.normalizePath(path);
524 
525             try
526             {
527                 if (path.startsWith(CLASSPATH_PREFIX))
528                 {
529                     url = ResourceUtils.resolveClasspathResource(path);
530                 }
531                 else
532                 {
533                     final File file = new File(path);
534                     url = file.exists() ? file.toURI().toURL() : new URL(path);
535                 }
536             }
537             catch (MalformedURLException exception)
538             {
539                 // ignore means no protocol was specified
540             }
541         }
542         return url;
543     }
544 
545     /**
546      * Resolves a URL to a classpath resource, this method will treat occurrences of the exclamation mark
547      * similar to what {@link URL} does with the <code>jar:file</code> protocol.
548      * <p/>
549      * Example: <code>my/path/to/some.zip!/file.xml</code> represents a resource <code>file.xml</code>
550      * that is located in a ZIP file on the classpath called <code>my/path/to/some.zip</code>
551      * <p/>
552      * It is possible to have nested ZIP files, example:
553      * <code>my/path/to/first.zip!/subdir/second.zip!/file.xml</code>.
554      * <p/>
555      * <i>Please note that the extension of the ZIP file can be anything,
556      * but in the case the extension is <code>.jar</code> the JVM will automatically unpack resources
557      * one level deep and put them all on the classpath</i>
558      *
559      * @param path the name of the resource to resolve to a URL, potentially nested in ZIP archives
560      * @return a URL pointing the resource resolved from the argument path
561      *      or <code>null</code> if the argument is <code>null</code> or impossible to resolve
562      */
563     public static URL resolveClasspathResource(String path)
564     {
565         URL urlResource = null;
566         if (path.startsWith(CLASSPATH_PREFIX))
567         {
568             path = path.substring(CLASSPATH_PREFIX.length(), path.length());
569 
570             // - the value of the following constant is -1 of no nested resources were specified,
571             //   otherwise it points to the location of the first occurrence
572             final int nestedPathOffset = path.indexOf("!/");
573 
574             // - take the part of the path that is not nested (may be all of it)
575             final String resourcePath = nestedPathOffset == -1 ? path : path.substring(0, nestedPathOffset);
576             final String nestingPath = nestedPathOffset == -1 ? "" : path.substring(nestedPathOffset);
577 
578             // - use the new path to load a URL from the classpath using the context class loader for this thread
579             urlResource = Thread.currentThread().getContextClassLoader().getResource(resourcePath);
580 
581             // - at this point the URL might be null in case the resource was not found
582             if (urlResource == null)
583             {
584                 if (logger.isDebugEnabled())
585                 {
586                     logger.debug("Resource could not be located on the classpath: " + resourcePath);
587                 }
588             }
589             else
590             {
591                 try
592                 {
593                     // - extract the filename from the entire resource path
594                     final int fileNameOffset = resourcePath.lastIndexOf('/');
595                     final String resourceFileName =
596                         fileNameOffset == -1 ? resourcePath : resourcePath.substring(fileNameOffset + 1);
597 
598                     if (logger.isDebugEnabled())
599                     {
600                         logger.debug("Creating temporary copy on the file system of the classpath resource");
601                     }
602                     final File fileSystemResource = File.createTempFile(resourceFileName, null);
603                     if (logger.isDebugEnabled())
604                     {
605                         logger.debug("Temporary file will be deleted on VM exit: " + fileSystemResource.getAbsolutePath());
606                     }
607                     fileSystemResource.deleteOnExit();
608                     if (logger.isDebugEnabled())
609                     {
610                         logger.debug("Copying classpath resource contents into temporary file");
611                     }
612                     writeUrlToFile(urlResource, fileSystemResource.toString());
613 
614                     // - count the times the actual resource to resolve has been nested
615                     final int nestingCount = StringUtils.countMatches(path, "!/");
616                     // - this buffer is used to construct the URL spec to that specific resource
617                     final StringBuilder buffer = new StringBuilder();
618                     for (int ctr = 0; ctr < nestingCount; ctr++)
619                     {
620                         buffer.append(ARCHIVE_PREFIX);
621                     }
622                     buffer.append(FILE_PREFIX).append(fileSystemResource.getAbsolutePath()).append(nestingPath);
623                     if (logger.isDebugEnabled())
624                     {
625                         logger.debug("Constructing URL to " +
626                             (nestingCount > 0 ? "nested" : "") + " resource in temporary file");
627                     }
628 
629                     urlResource = new URL(buffer.toString());
630                 }
631                 catch (final IOException exception)
632                 {
633                     logger.warn("Unable to resolve classpath resource", exception);
634                     // - impossible to properly resolve the path into a URL
635                     urlResource = null;
636                 }
637             }
638         }
639         return urlResource;
640     }
641 
642     /**
643      * Writes the URL contents to a file specified by the fileLocation argument.
644      *
645      * @param url the URL to read
646      * @param fileLocation the location which to write.
647      * @throws IOException if error writing file
648      */
649     public static void writeUrlToFile(final URL url, final String fileLocation)
650         throws IOException
651     {
652         ExceptionUtils.checkNull(
653             "url",
654             url);
655         ExceptionUtils.checkEmpty(
656             "fileLocation",
657             fileLocation);
658 
659         final File lOutputFile = new File(fileLocation);
660         makeDirectories(lOutputFile);
661         FileUtils.copyURLToFile(url, lOutputFile);
662     }
663 
664     /**
665      * Indicates whether or not the given <code>url</code> is a file.
666      *
667      * @param url the URL to check.
668      * @return true/false
669      */
670     public static boolean isFile(final URL url)
671     {
672         return url != null && new File(url.getFile()).isFile();
673     }
674 
675     /**
676      * The forward slash character.
677      */
678     private static final String FORWARD_SLASH = "/";
679 
680     /**
681      * Gets the contents of this directory and any of its sub directories based on the given <code>patterns</code>.
682      * And returns absolute or relative paths depending on the value of <code>absolute</code>.
683      *
684      * @param url the URL of the directory.
685      * @param absolute whether or not the returned content paths should be absolute (if
686      *        false paths will be relative to URL).
687      * @param patterns
688      * @return a collection of paths.
689      */
690     public static List<String> getDirectoryContents(
691         final URL url,
692         boolean absolute,
693         final String[] patterns)
694     {
695         List<String> contents = ResourceUtils.getDirectoryContents(
696                 url,
697                 0,
698                 true);
699 
700         // - first see if it's a directory
701         if (!contents.isEmpty())
702         {
703             for (final ListIterator<String> iterator = contents.listIterator(); iterator.hasNext();)
704             {
705                 String path = iterator.next();
706                 if (!matchesAtLeastOnePattern(
707                         path,
708                         patterns))
709                 {
710                     iterator.remove();
711                 }
712                 else if (absolute)
713                 {
714                     path = url.toString().endsWith(FORWARD_SLASH) ? path : FORWARD_SLASH + path;
715                     final URL resource = ResourceUtils.toURL(url + path);
716                     if (resource != null)
717                     {
718                         iterator.set(resource.toString());
719                     }
720                 }
721             }
722         }
723         else // - otherwise handle archives (i.e. jars, etc).
724         {
725             final String urlAsString = url.toString();
726             final String delimiter = "!/";
727             final String archivePath = urlAsString.replaceAll(
728                     delimiter + ".*",
729                     delimiter);
730             contents = ResourceUtils.getClassPathArchiveContents(url);
731             for (final ListIterator<String> iterator = contents.listIterator(); iterator.hasNext();)
732             {
733                 final String relativePath = iterator.next();
734                 final String fullPath = archivePath + relativePath;
735                 if (!fullPath.startsWith(urlAsString) || fullPath.equals(urlAsString + FORWARD_SLASH))
736                 {
737                     iterator.remove();
738                 }
739                 else if (!matchesAtLeastOnePattern(
740                         relativePath,
741                         patterns))
742                 {
743                     iterator.remove();
744                 }
745                 else if (absolute)
746                 {
747                     iterator.set(fullPath);
748                 }
749             }
750         }
751         return contents;
752     }
753 
754     /**
755      * Indicates whether or not the given <code>path</code> matches on
756      * one or more of the patterns defined within this class
757      * returns true if no patterns are defined.
758      *
759      * @param path the path to match on.
760      * @param patterns
761      * @return true/false
762      */
763     public static boolean matchesAtLeastOnePattern(
764         final String path,
765         final String[] patterns)
766     {
767         boolean matches = (patterns == null || patterns.length == 0);
768         if (!matches && patterns != null && patterns.length > 0)
769         {
770             final int patternNumber = patterns.length;
771             for (int ctr = 0; ctr < patternNumber; ctr++)
772             {
773                 final String pattern = patterns[ctr];
774                 if (PathMatcher.wildcardMatch(
775                         path,
776                         pattern))
777                 {
778                     matches = true;
779                     break;
780                 }
781             }
782         }
783         return matches;
784     }
785 
786     /**
787      * Indicates whether or not the contents of the given <code>directory</code>
788      * and any of its sub directories have been modified after the given <code>time</code>.
789      *
790      * @param directory the directory to check
791      * @param time the time to check against
792      * @return true/false
793      */
794     public static boolean modifiedAfter(
795         long time,
796         final File directory)
797     {
798         final Collection<File> files = ResourceUtils.loadFiles(directory, true);
799         boolean changed = files.isEmpty();
800         for (File file : files)
801         {
802             changed = file.lastModified() < time;
803             if (changed)
804             {
805                 break;
806             }
807         }
808         return changed;
809     }
810 
811     /**
812      * The pattern used for normalizing paths paths with more than one back slash.
813      */
814     private static final String BACK_SLASH_NORMALIZATION_PATTERN = "\\\\+";
815 
816     /**
817      * The pattern used for normalizing paths with more than one forward slash.
818      */
819     private static final String FORWARD_SLASH_NORMALIZATION_PATTERN = FORWARD_SLASH + '+';
820 
821     /**
822      * Removes any extra path separators and converts all from back slashes
823      * to forward slashes.
824      *
825      * @param path the path to normalize.
826      * @return the normalizd path
827      */
828     public static String normalizePath(final String path)
829     {
830         return path != null
831         ? path.replaceAll(
832             BACK_SLASH_NORMALIZATION_PATTERN,
833             FORWARD_SLASH).replaceAll(
834             FORWARD_SLASH_NORMALIZATION_PATTERN,
835             FORWARD_SLASH) : null;
836     }
837 
838     /**
839      * Takes a path and replaces the oldException with the newExtension.
840      *
841      * @param path the path to rename.
842      * @param oldExtension the extension to rename from.
843      * @param newExtension the extension to rename to.
844      * @return the path with the new extension.
845      */
846     public static String renameExtension(
847         final String path,
848         final String oldExtension,
849         final String newExtension)
850     {
851         ExceptionUtils.checkEmpty(
852             "path",
853             path);
854         ExceptionUtils.checkNull(
855             "oldExtension",
856             oldExtension);
857         ExceptionUtils.checkNull(
858             "newExtension",
859             newExtension);
860         String newPath = path;
861         final int oldExtensionIndex = path.lastIndexOf(oldExtension);
862         if (oldExtensionIndex != -1)
863         {
864             newPath = path.substring(
865                     0,
866                     oldExtensionIndex) + newExtension;
867         }
868         return newPath;
869     }
870 }