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 }