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 * <p>
488 * If the directory is specified and is a valid directory then an attempt at finding the resource by appending the
489 * <code>resourceName</code> to the given <code>directory</code> will be made, otherwise an attempt to find the
490 * <code>resourceName</code> directly on the classpath will be initiated. </p>
491 *
492 * @param resourceName the name of a resource
493 * @param directory the directory location
494 * @return the resource url
495 */
496 public static URL getResource(
497 final String resourceName,
498 final URL directory)
499 {
500 String directoryLocation = null;
501 if (directory != null)
502 {
503 directoryLocation = directory.getFile();
504 }
505 return getResource(
506 resourceName,
507 directoryLocation);
508 }
509
510 /**
511 * Attempts to construct the given <code>path</code>
512 * to a URL instance. If the argument cannot be resolved as a resource
513 * on the file system this method will attempt to locate it on the
514 * classpath.
515 *
516 * @param path the path from which to construct the URL.
517 * @return the constructed URL or null if one couldn't be constructed.
518 */
519 public static URL toURL(String path)
520 {
521 URL url = null;
522 if (path != null)
523 {
524 path = ResourceUtils.normalizePath(path);
525
526 try
527 {
528 if (path.startsWith(CLASSPATH_PREFIX))
529 {
530 url = ResourceUtils.resolveClasspathResource(path);
531 }
532 else
533 {
534 final File file = new File(path);
535 url = file.exists() ? file.toURI().toURL() : new URL(path);
536 }
537 }
538 catch (MalformedURLException exception)
539 {
540 // ignore means no protocol was specified
541 }
542 }
543 return url;
544 }
545
546 /**
547 * Resolves a URL to a classpath resource, this method will treat occurrences of the exclamation mark
548 * similar to what {@link URL} does with the <code>jar:file</code> protocol.
549 * <p>
550 * Example: <code>my/path/to/some.zip!/file.xml</code> represents a resource <code>file.xml</code>
551 * that is located in a ZIP file on the classpath called <code>my/path/to/some.zip</code>
552 * <p>
553 * It is possible to have nested ZIP files, example:
554 * <code>my/path/to/first.zip!/subdir/second.zip!/file.xml</code>.
555 * <p>
556 * <i>Please note that the extension of the ZIP file can be anything,
557 * but in the case the extension is <code>.jar</code> the JVM will automatically unpack resources
558 * one level deep and put them all on the classpath</i>
559 *
560 * @param path the name of the resource to resolve to a URL, potentially nested in ZIP archives
561 * @return a URL pointing the resource resolved from the argument path
562 * or <code>null</code> if the argument is <code>null</code> or impossible to resolve
563 */
564 public static URL resolveClasspathResource(String path)
565 {
566 URL urlResource = null;
567 if (path.startsWith(CLASSPATH_PREFIX))
568 {
569 path = path.substring(CLASSPATH_PREFIX.length(), path.length());
570
571 // - the value of the following constant is -1 of no nested resources were specified,
572 // otherwise it points to the location of the first occurrence
573 final int nestedPathOffset = path.indexOf("!/");
574
575 // - take the part of the path that is not nested (may be all of it)
576 final String resourcePath = nestedPathOffset == -1 ? path : path.substring(0, nestedPathOffset);
577 final String nestingPath = nestedPathOffset == -1 ? "" : path.substring(nestedPathOffset);
578
579 // - use the new path to load a URL from the classpath using the context class loader for this thread
580 urlResource = Thread.currentThread().getContextClassLoader().getResource(resourcePath);
581
582 // - at this point the URL might be null in case the resource was not found
583 if (urlResource == null)
584 {
585 if (logger.isDebugEnabled())
586 {
587 logger.debug("Resource could not be located on the classpath: " + resourcePath);
588 }
589 }
590 else
591 {
592 try
593 {
594 // - extract the filename from the entire resource path
595 final int fileNameOffset = resourcePath.lastIndexOf('/');
596 final String resourceFileName =
597 fileNameOffset == -1 ? resourcePath : resourcePath.substring(fileNameOffset + 1);
598
599 if (logger.isDebugEnabled())
600 {
601 logger.debug("Creating temporary copy on the file system of the classpath resource");
602 }
603 final File fileSystemResource = File.createTempFile(resourceFileName, null);
604 if (logger.isDebugEnabled())
605 {
606 logger.debug("Temporary file will be deleted on VM exit: " + fileSystemResource.getAbsolutePath());
607 }
608 fileSystemResource.deleteOnExit();
609 if (logger.isDebugEnabled())
610 {
611 logger.debug("Copying classpath resource contents into temporary file");
612 }
613 writeUrlToFile(urlResource, fileSystemResource.toString());
614
615 // - count the times the actual resource to resolve has been nested
616 final int nestingCount = StringUtils.countMatches(path, "!/");
617 // - this buffer is used to construct the URL spec to that specific resource
618 final StringBuilder buffer = new StringBuilder();
619 for (int ctr = 0; ctr < nestingCount; ctr++)
620 {
621 buffer.append(ARCHIVE_PREFIX);
622 }
623 buffer.append(FILE_PREFIX).append(fileSystemResource.getAbsolutePath()).append(nestingPath);
624 if (logger.isDebugEnabled())
625 {
626 logger.debug("Constructing URL to " +
627 (nestingCount > 0 ? "nested" : "") + " resource in temporary file");
628 }
629
630 urlResource = new URL(buffer.toString());
631 }
632 catch (final IOException exception)
633 {
634 logger.warn("Unable to resolve classpath resource", exception);
635 // - impossible to properly resolve the path into a URL
636 urlResource = null;
637 }
638 }
639 }
640 return urlResource;
641 }
642
643 /**
644 * Writes the URL contents to a file specified by the fileLocation argument.
645 *
646 * @param url the URL to read
647 * @param fileLocation the location which to write.
648 * @throws IOException if error writing file
649 */
650 public static void writeUrlToFile(final URL url, final String fileLocation)
651 throws IOException
652 {
653 ExceptionUtils.checkNull(
654 "url",
655 url);
656 ExceptionUtils.checkEmpty(
657 "fileLocation",
658 fileLocation);
659
660 final File lOutputFile = new File(fileLocation);
661 makeDirectories(lOutputFile);
662 FileUtils.copyURLToFile(url, lOutputFile);
663 }
664
665 /**
666 * Indicates whether or not the given <code>url</code> is a file.
667 *
668 * @param url the URL to check.
669 * @return true/false
670 */
671 public static boolean isFile(final URL url)
672 {
673 return url != null && new File(url.getFile()).isFile();
674 }
675
676 /**
677 * The forward slash character.
678 */
679 private static final String FORWARD_SLASH = "/";
680
681 /**
682 * Gets the contents of this directory and any of its sub directories based on the given <code>patterns</code>.
683 * And returns absolute or relative paths depending on the value of <code>absolute</code>.
684 *
685 * @param url the URL of the directory.
686 * @param absolute whether or not the returned content paths should be absolute (if
687 * false paths will be relative to URL).
688 * @param patterns
689 * @return a collection of paths.
690 */
691 public static List<String> getDirectoryContents(
692 final URL url,
693 boolean absolute,
694 final String[] patterns)
695 {
696 List<String> contents = ResourceUtils.getDirectoryContents(
697 url,
698 0,
699 true);
700
701 // - first see if it's a directory
702 if (!contents.isEmpty())
703 {
704 for (final ListIterator<String> iterator = contents.listIterator(); iterator.hasNext();)
705 {
706 String path = iterator.next();
707 if (!matchesAtLeastOnePattern(
708 path,
709 patterns))
710 {
711 iterator.remove();
712 }
713 else if (absolute)
714 {
715 path = url.toString().endsWith(FORWARD_SLASH) ? path : FORWARD_SLASH + path;
716 final URL resource = ResourceUtils.toURL(url + path);
717 if (resource != null)
718 {
719 iterator.set(resource.toString());
720 }
721 }
722 }
723 }
724 else // - otherwise handle archives (i.e. jars, etc).
725 {
726 final String urlAsString = url.toString();
727 final String delimiter = "!/";
728 final String archivePath = urlAsString.replaceAll(
729 delimiter + ".*",
730 delimiter);
731 contents = ResourceUtils.getClassPathArchiveContents(url);
732 for (final ListIterator<String> iterator = contents.listIterator(); iterator.hasNext();)
733 {
734 final String relativePath = iterator.next();
735 final String fullPath = archivePath + relativePath;
736 if (!fullPath.startsWith(urlAsString) || fullPath.equals(urlAsString + FORWARD_SLASH))
737 {
738 iterator.remove();
739 }
740 else if (!matchesAtLeastOnePattern(
741 relativePath,
742 patterns))
743 {
744 iterator.remove();
745 }
746 else if (absolute)
747 {
748 iterator.set(fullPath);
749 }
750 }
751 }
752 return contents;
753 }
754
755 /**
756 * Indicates whether or not the given <code>path</code> matches on
757 * one or more of the patterns defined within this class
758 * returns true if no patterns are defined.
759 *
760 * @param path the path to match on.
761 * @param patterns
762 * @return true/false
763 */
764 public static boolean matchesAtLeastOnePattern(
765 final String path,
766 final String[] patterns)
767 {
768 boolean matches = (patterns == null || patterns.length == 0);
769 if (!matches && patterns != null && patterns.length > 0)
770 {
771 final int patternNumber = patterns.length;
772 for (int ctr = 0; ctr < patternNumber; ctr++)
773 {
774 final String pattern = patterns[ctr];
775 if (PathMatcher.wildcardMatch(
776 path,
777 pattern))
778 {
779 matches = true;
780 break;
781 }
782 }
783 }
784 return matches;
785 }
786
787 /**
788 * Indicates whether or not the contents of the given <code>directory</code>
789 * and any of its sub directories have been modified after the given <code>time</code>.
790 *
791 * @param directory the directory to check
792 * @param time the time to check against
793 * @return true/false
794 */
795 public static boolean modifiedAfter(
796 long time,
797 final File directory)
798 {
799 final Collection<File> files = ResourceUtils.loadFiles(directory, true);
800 boolean changed = files.isEmpty();
801 for (File file : files)
802 {
803 changed = file.lastModified() < time;
804 if (changed)
805 {
806 break;
807 }
808 }
809 return changed;
810 }
811
812 /**
813 * The pattern used for normalizing paths paths with more than one back slash.
814 */
815 private static final String BACK_SLASH_NORMALIZATION_PATTERN = "\\\\+";
816
817 /**
818 * The pattern used for normalizing paths with more than one forward slash.
819 */
820 private static final String FORWARD_SLASH_NORMALIZATION_PATTERN = FORWARD_SLASH + '+';
821
822 /**
823 * Removes any extra path separators and converts all from back slashes
824 * to forward slashes.
825 *
826 * @param path the path to normalize.
827 * @return the normalizd path
828 */
829 public static String normalizePath(final String path)
830 {
831 return path != null
832 ? path.replaceAll(
833 BACK_SLASH_NORMALIZATION_PATTERN,
834 FORWARD_SLASH).replaceAll(
835 FORWARD_SLASH_NORMALIZATION_PATTERN,
836 FORWARD_SLASH) : null;
837 }
838
839 /**
840 * Takes a path and replaces the oldException with the newExtension.
841 *
842 * @param path the path to rename.
843 * @param oldExtension the extension to rename from.
844 * @param newExtension the extension to rename to.
845 * @return the path with the new extension.
846 */
847 public static String renameExtension(
848 final String path,
849 final String oldExtension,
850 final String newExtension)
851 {
852 ExceptionUtils.checkEmpty(
853 "path",
854 path);
855 ExceptionUtils.checkNull(
856 "oldExtension",
857 oldExtension);
858 ExceptionUtils.checkNull(
859 "newExtension",
860 newExtension);
861 String newPath = path;
862 final int oldExtensionIndex = path.lastIndexOf(oldExtension);
863 if (oldExtensionIndex != -1)
864 {
865 newPath = path.substring(
866 0,
867 oldExtensionIndex) + newExtension;
868 }
869 return newPath;
870 }
871 }