001package org.andromda.core.common; 002 003import java.io.File; 004import java.io.FileNotFoundException; 005import java.io.IOException; 006import java.io.InputStream; 007import java.io.Reader; 008import java.net.MalformedURLException; 009import java.net.URL; 010import java.net.URLConnection; 011import java.net.URLDecoder; 012import java.util.ArrayList; 013import java.util.Collection; 014import java.util.Collections; 015import java.util.Enumeration; 016import java.util.List; 017import java.util.ListIterator; 018import java.util.zip.ZipEntry; 019import java.util.zip.ZipFile; 020 021import org.apache.commons.io.FileUtils; 022import org.apache.commons.io.IOUtils; 023import org.apache.commons.io.filefilter.TrueFileFilter; 024import org.apache.commons.lang.StringUtils; 025import org.apache.log4j.Logger; 026 027/** 028 * Provides utilities for loading resources. 029 * 030 * @author Chad Brandon 031 * @author Bob Fields 032 * @author Michail Plushnikov 033 */ 034public class ResourceUtils 035{ 036 private static final Logger logger = Logger.getLogger(ResourceUtils.class); 037 038 /** 039 * All archive files start with this prefix. 040 */ 041 private static final String ARCHIVE_PREFIX = "jar:"; 042 043 /** 044 * The prefix for URL file resources. 045 */ 046 private static final String FILE_PREFIX = "file:"; 047 048 /** 049 * The prefix to use for searching the classpath for a resource. 050 */ 051 private static final String CLASSPATH_PREFIX = "classpath:"; 052 053 /** 054 * Retrieves a resource from the current classpath. 055 * 056 * @param resourceName the name of the resource 057 * @return the resource url 058 */ 059 public static URL getResource(final String resourceName) 060 { 061 ExceptionUtils.checkEmpty( 062 "resourceName", 063 resourceName); 064 final ClassLoader loader = ClassUtils.getClassLoader(); 065 URL resource = loader != null ? loader.getResource(resourceName) : null; 066 //if (resource==null) 067 return resource; 068 } 069 070 /** 071 * Loads the resource and returns the contents as a String. 072 * 073 * @param resource the name of the resource. 074 * @return String 075 */ 076 public static String getContents(final URL resource) 077 { 078 String result = null; 079 080 InputStream in = null; 081 try 082 { 083 if(null!=resource) 084 { 085 in = resource.openStream(); 086 result = IOUtils.toString(in); 087 } 088 } 089 catch (final IOException ex) { 090 throw new RuntimeException(ex); 091 }finally { 092 IOUtils.closeQuietly(in); 093 } 094 return result; 095 } 096 097 /** 098 * Loads the resource and returns the contents as a String. 099 * 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}