001: package org.andromda.core.common;
002:
003: import java.io.BufferedInputStream;
004: import java.io.BufferedOutputStream;
005: import java.io.BufferedReader;
006: import java.io.File;
007: import java.io.FileOutputStream;
008: import java.io.InputStream;
009: import java.io.InputStreamReader;
010: import java.io.OutputStream;
011: import java.io.Reader;
012: import java.io.IOException;
013:
014: import java.net.MalformedURLException;
015: import java.net.URL;
016: import java.net.URLConnection;
017: import java.net.URLDecoder;
018:
019: import java.util.ArrayList;
020: import java.util.Enumeration;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.ListIterator;
024: import java.util.zip.ZipEntry;
025: import java.util.zip.ZipFile;
026:
027: import org.apache.commons.lang.StringUtils;
028: import org.apache.log4j.Logger;
029:
030: /**
031: * Provides utilities for loading resources.
032: *
033: * @author Chad Brandon
034: */
035: public class ResourceUtils {
036: private final static Logger logger = Logger
037: .getLogger(ResourceUtils.class);
038:
039: /**
040: * All archive files start with this prefix.
041: */
042: private static final String ARCHIVE_PREFIX = "jar:";
043:
044: /**
045: * The prefix for URL file resources.
046: */
047: private static final String FILE_PREFIX = "file:";
048:
049: /**
050: * The prefix to use for searching the classpath for a resource.
051: */
052: private static final String CLASSPATH_PREFIX = "classpath:";
053:
054: /**
055: * Retrieves a resource from the current classpath.
056: *
057: * @param resourceName the name of the resource
058: * @return the resource url
059: */
060: public static URL getResource(final String resourceName) {
061: ExceptionUtils.checkEmpty("resourceName", resourceName);
062: final ClassLoader loader = ClassUtils.getClassLoader();
063: return loader != null ? loader.getResource(resourceName) : null;
064: }
065:
066: /**
067: * Loads the resource and returns the contents as a String.
068: *
069: * @param resource the name of the resource.
070: * @return String
071: */
072: public static String getContents(final URL resource) {
073: try {
074: return getContents(resource != null ? new InputStreamReader(
075: resource.openStream())
076: : null);
077: } catch (final Throwable throwable) {
078: throw new RuntimeException(throwable);
079: }
080: }
081:
082: /**
083: * The line separator.
084: */
085: private static final char LINE_SEPARATOR = '\n';
086:
087: /**
088: * Loads the resource and returns the contents as a String.
089: *
090: * @param resource the name of the resource.
091: * @return the contents of the resource as a string.
092: */
093: public static String getContents(final Reader resource) {
094: final StringBuffer contents = new StringBuffer();
095: try {
096: if (resource != null) {
097: BufferedReader resourceInput = new BufferedReader(
098: resource);
099: for (String line = resourceInput.readLine(); line != null; line = resourceInput
100: .readLine()) {
101: contents.append(line).append(LINE_SEPARATOR);
102: }
103: resourceInput.close();
104: resourceInput = null;
105: }
106: } catch (final Throwable throwable) {
107: throw new RuntimeException(throwable);
108: }
109: return contents.toString().trim();
110: }
111:
112: /**
113: * If the <code>resource</code> represents a classpath archive (i.e. jar, zip, etc), this method will retrieve all
114: * contents from that resource as a List of relative paths (relative to the archive base). Otherwise an empty List
115: * will be returned.
116: *
117: * @param resource the resource from which to retrieve the contents
118: * @return a list of Strings containing the names of every nested resource found in this resource.
119: */
120: public static List getClassPathArchiveContents(final URL resource) {
121: final List contents = new ArrayList();
122: if (isArchive(resource)) {
123: final ZipFile archive = getArchive(resource);
124: if (archive != null) {
125: for (final Enumeration entries = archive.entries(); entries
126: .hasMoreElements();) {
127: final ZipEntry entry = (ZipEntry) entries
128: .nextElement();
129: contents.add(entry.getName());
130: }
131: }
132: }
133: return contents;
134: }
135:
136: /**
137: * If this <code>resource</code> happens to be a directory, it will load the contents of that directory into a
138: * List and return the list of names relative to the given <code>resource</code> (otherwise it will return an empty
139: * List).
140: *
141: * @param resource the resource from which to retrieve the contents
142: * @param levels the number of levels to step down if the resource ends up being a directory (if its an artifact,
143: * levels will be ignored).
144: * @return a list of Strings containing the names of every nested resource found in this resource.
145: */
146: public static List getDirectoryContents(final URL resource,
147: final int levels) {
148: return getDirectoryContents(resource, levels, true);
149: }
150:
151: /**
152: * The character used for substituing whitespace in paths.
153: */
154: private static final String PATH_WHITESPACE_CHARACTER = "%20";
155:
156: /**
157: * Replaces any escape characters in the given file path with their
158: * counterparts.
159: *
160: * @param filePath the path of the file to unescape.
161: * @return the unescaped path.
162: */
163: public static String unescapeFilePath(String filePath) {
164: if (filePath != null && filePath.length() > 0) {
165: filePath = filePath.replaceAll(PATH_WHITESPACE_CHARACTER,
166: " ");
167: }
168: return filePath;
169: }
170:
171: /**
172: * If this <code>resource</code> happens to be a directory, it will load the contents of that directory into a
173: * List and return the list of names relative to the given <code>resource</code> (otherwise it will return an empty
174: * List).
175: *
176: * @param resource the resource from which to retrieve the contents
177: * @param levels the number of levels to step down if the resource ends up being a directory (if its an artifact,
178: * levels will be ignored).
179: * @param includeSubdirectories whether or not to include subdirectories in the contents.
180: * @return a list of Strings containing the names of every nested resource found in this resource.
181: */
182: public static List getDirectoryContents(final URL resource,
183: final int levels, boolean includeSubdirectories) {
184: final List contents = new ArrayList();
185: if (resource != null) {
186: // - create the file and make sure we remove any path white space characters
187: final File fileResource = new File(
188: unescapeFilePath(resource.getFile()));
189: if (fileResource.isDirectory()) {
190: File rootDirectory = fileResource;
191: for (int ctr = 0; ctr < levels; ctr++) {
192: rootDirectory = rootDirectory.getParentFile();
193: }
194: final File pluginDirectory = rootDirectory;
195: loadFiles(pluginDirectory, contents,
196: includeSubdirectories);
197:
198: // - remove the root path from each file
199: for (final ListIterator iterator = contents
200: .listIterator(); iterator.hasNext();) {
201: iterator.set(StringUtils.replace(((File) iterator
202: .next()).getPath().replace('\\', '/'),
203: pluginDirectory.getPath()
204: .replace('\\', '/') + '/', ""));
205: }
206: }
207: }
208: return contents;
209: }
210:
211: /**
212: * Loads all files find in the <code>directory</code> and adds them to the <code>fileList</code>.
213: *
214: * @param directory the directory from which to load all files.
215: * @param fileList the List of files to which we'll add the found files.
216: * @param includeSubdirectories whether or not to include sub directories when loading the files.
217: */
218: private static void loadFiles(final File directory,
219: final List fileList, boolean includeSubdirectories) {
220: if (directory != null) {
221: final File[] files = directory.listFiles();
222: if (files != null) {
223: for (int ctr = 0; ctr < files.length; ctr++) {
224: File file = files[ctr];
225: if (!file.isDirectory()) {
226: fileList.add(file);
227: } else if (includeSubdirectories) {
228: loadFiles(file, fileList, includeSubdirectories);
229: }
230: }
231: }
232: }
233: }
234:
235: /**
236: * Returns true/false on whether or not this <code>resource</code> represents an archive or not (i.e. jar, or zip,
237: * etc).
238: *
239: * @return true if its an archive, false otherwise.
240: */
241: public static boolean isArchive(final URL resource) {
242: return resource != null
243: && resource.toString().startsWith(ARCHIVE_PREFIX);
244: }
245:
246: private static final String URL_DECODE_ENCODING = "UTF-8";
247:
248: /**
249: * If this <code>resource</code> is an archive file, it will return the resource as an archive.
250: *
251: * @return the archive as a ZipFile
252: */
253: public static ZipFile getArchive(final URL resource) {
254: try {
255: ZipFile archive = null;
256: if (resource != null) {
257: String resourceUrl = resource.toString();
258: resourceUrl = resourceUrl.replaceFirst(ARCHIVE_PREFIX,
259: "");
260: final int entryPrefixIndex = resourceUrl.indexOf('!');
261: if (entryPrefixIndex != -1) {
262: resourceUrl = resourceUrl.substring(0,
263: entryPrefixIndex);
264: }
265: resourceUrl = URLDecoder.decode(new URL(resourceUrl)
266: .getFile(), URL_DECODE_ENCODING);
267: archive = new ZipFile(resourceUrl);
268: }
269: return archive;
270: } catch (final Throwable throwable) {
271: throw new RuntimeException(throwable);
272: }
273: }
274:
275: /**
276: * Loads the file resource and returns the contents as a String.
277: *
278: * @param resourceName the name of the resource.
279: * @return String
280: */
281: public static String getContents(final String resourceName) {
282: return getContents(getResource(resourceName));
283: }
284:
285: /**
286: * Takes a className as an argument and returns the URL for the class.
287: *
288: * @param className
289: * @return java.net.URL
290: */
291: public static URL getClassResource(final String className) {
292: ExceptionUtils.checkEmpty("className", className);
293: return getResource(getClassNameAsResource(className));
294: }
295:
296: /**
297: * Gets the class name as a resource.
298: *
299: * @param className the name of the class.
300: * @return the class name as a resource path.
301: */
302: private static String getClassNameAsResource(final String className) {
303: return className.replace('.', '/') + ".class";
304: }
305:
306: /**
307: * <p/>
308: * Retrieves a resource from an optionally given <code>directory</code> or from the package on the classpath. </p>
309: * <p/>
310: * If the directory is specified and is a valid directory then an attempt at finding the resource by appending the
311: * <code>resourceName</code> to the given <code>directory</code> will be made, otherwise an attempt to find the
312: * <code>resourceName</code> directly on the classpath will be initiated. </p>
313: *
314: * @param resourceName the name of a resource
315: * @param directory the directory location
316: * @return the resource url
317: */
318: public static URL getResource(final String resourceName,
319: final String directory) {
320: ExceptionUtils.checkEmpty("resourceName", resourceName);
321:
322: if (directory != null) {
323: final File file = new File(directory, resourceName);
324: if (file.exists()) {
325: try {
326: return file.toURL();
327: } catch (final MalformedURLException exception) {
328: // - ignore, we just try to find the resource on the classpath
329: }
330: }
331: }
332: return getResource(resourceName);
333: }
334:
335: /**
336: * Makes the directory for the given location if it doesn't exist.
337: *
338: * @param location the location to make the directory.
339: */
340: public static void makeDirectories(final String location) {
341: final File file = new File(location);
342: final File parent = file.getParentFile();
343: if (parent != null) {
344: parent.mkdirs();
345: }
346: }
347:
348: /**
349: * Gets the time as a <code>long</code> when this <code>resource</code> was last modified.
350: * If it can not be determined <code>0</code> is returned.
351: *
352: * @param resource the resource from which to retrieve
353: * the last modified time.
354: * @return the last modified time or 0 if it couldn't be retrieved.
355: */
356: public static long getLastModifiedTime(final URL resource) {
357: long lastModified;
358: try {
359: final File file = new File(resource.getFile());
360: if (file.exists()) {
361: lastModified = file.lastModified();
362: } else {
363: URLConnection uriConnection = resource.openConnection();
364: lastModified = uriConnection.getLastModified();
365:
366: // - we need to set the urlConnection to null and explicity
367: // call garbage collection, otherwise the JVM won't let go
368: // of the URL resource
369: uriConnection = null;
370: System.gc();
371: }
372: } catch (final Exception exception) {
373: lastModified = 0;
374: }
375: return lastModified;
376: }
377:
378: /**
379: * <p>
380: * Retrieves a resource from an optionally given <code>directory</code> or from the package on the classpath.
381: * </p>
382: * If the directory is specified and is a valid directory then an attempt at finding the resource by appending the
383: * <code>resourceName</code> to the given <code>directory</code> will be made, otherwise an attempt to find the
384: * <code>resourceName</code> directly on the classpath will be initiated. </p>
385: *
386: * @param resourceName the name of a resource
387: * @param directory the directory location
388: * @return the resource url
389: */
390: public static URL getResource(final String resourceName,
391: final URL directory) {
392: String directoryLocation = null;
393: if (directory != null) {
394: directoryLocation = directory.getFile();
395: }
396: return getResource(resourceName, directoryLocation);
397: }
398:
399: /**
400: * Attempts to construct the given <code>path</code>
401: * to a URL instance. If the argument cannot be resolved as a resource
402: * on the file system this method will attempt to locate it on the
403: * classpath.
404: *
405: * @param path the path from which to construct the URL.
406: * @return the constructed URL or null if one couldn't be constructed.
407: */
408: public static URL toURL(String path) {
409: URL url = null;
410: if (path != null) {
411: path = ResourceUtils.normalizePath(path);
412:
413: try {
414: if (path.startsWith(CLASSPATH_PREFIX)) {
415: url = ResourceUtils.resolveClasspathResource(path);
416: } else {
417: final File file = new File(path);
418: url = file.exists() ? file.toURL() : new URL(path);
419: }
420: } catch (MalformedURLException exception) {
421: // ignore means no protocol was specified
422: }
423: }
424: return url;
425: }
426:
427: /**
428: * Resolves a URL to a classpath resource, this method will treat occurrences of the exclamation mark
429: * similar to what {@link URL} does with the <code>jar:file</code> protocol.
430: * <p/>
431: * Example: <code>my/path/to/some.zip!/file.xml</code> represents a resource <code>file.xml</code>
432: * that is located in a ZIP file on the classpath called <code>my/path/to/some.zip</code>
433: * <p/>
434: * It is possible to have nested ZIP files, example:
435: * <code>my/path/to/first.zip!/subdir/second.zip!/file.xml</code>.
436: * <p/>
437: * <i>Please note that the extension of the ZIP file can be anything,
438: * but in the case the extension is <code>.jar</code> the JVM will automatically unpack resources
439: * one level deep and put them all on the classpath</i>
440: *
441: * @param path the name of the resource to resolve to a URL, potentially nested in ZIP archives
442: * @return a URL pointing the resource resolved from the argument path
443: * or <code>null</code> if the argument is <code>null</code> or impossible to resolve
444: */
445: public static URL resolveClasspathResource(String path) {
446: URL urlResource = null;
447: if (path.startsWith(CLASSPATH_PREFIX)) {
448: path = path.substring(CLASSPATH_PREFIX.length(), path
449: .length());
450:
451: // - the value of the following constant is -1 of no nested resources were specified,
452: // otherwise it points to the location of the first occurrence
453: final int nestedPathOffset = path.indexOf("!/");
454:
455: // - take the part of the path that is not nested (may be all of it)
456: final String resourcePath = nestedPathOffset == -1 ? path
457: : path.substring(0, nestedPathOffset);
458: final String nestingPath = nestedPathOffset == -1 ? ""
459: : path.substring(nestedPathOffset);
460:
461: // - use the new path to load a URL from the classpath using the context class loader for this thread
462: urlResource = Thread.currentThread()
463: .getContextClassLoader().getResource(resourcePath);
464:
465: // - at this point the URL might be null in case the resource was not found
466: if (urlResource == null) {
467: if (logger.isDebugEnabled()) {
468: logger
469: .debug("Resource could not be located on the classpath: "
470: + resourcePath);
471: }
472: } else {
473: try {
474: // - extract the filename from the entire resource path
475: final int fileNameOffset = resourcePath
476: .lastIndexOf('/');
477: final String resourceFileName = fileNameOffset == -1 ? resourcePath
478: : resourcePath
479: .substring(fileNameOffset + 1);
480:
481: if (logger.isDebugEnabled()) {
482: logger
483: .debug("Creating temporary copy on the file system of the classpath resource");
484: }
485: final File fileSystemResource = File
486: .createTempFile(resourceFileName, null);
487: if (logger.isDebugEnabled()) {
488: logger
489: .debug("Temporary file will be deleted on VM exit: "
490: + fileSystemResource
491: .getAbsolutePath());
492: }
493: fileSystemResource.deleteOnExit();
494: if (logger.isDebugEnabled()) {
495: logger
496: .debug("Copying classpath resource contents into temporary file");
497: }
498: writeUrlToFile(urlResource, fileSystemResource
499: .toString(), null);
500:
501: // - count the times the actual resource to resolve has been nested
502: final int nestingCount = StringUtils.countMatches(
503: path, "!/");
504: // - this buffer is used to construct the URL spec to that specific resource
505: final StringBuffer buffer = new StringBuffer();
506: for (int ctr = 0; ctr < nestingCount; ctr++) {
507: buffer.append(ARCHIVE_PREFIX);
508: }
509: buffer.append(FILE_PREFIX).append(
510: fileSystemResource.getAbsolutePath())
511: .append(nestingPath);
512: if (logger.isDebugEnabled()) {
513: logger.debug("Constructing URL to "
514: + (nestingCount > 0 ? "nested" : "")
515: + " resource in temporary file");
516: }
517:
518: urlResource = new URL(buffer.toString());
519: } catch (final IOException exception) {
520: logger.warn("Unable to resolve classpath resource",
521: exception);
522: // - impossible to properly resolve the path into a URL
523: urlResource = null;
524: }
525: }
526: }
527: return urlResource;
528: }
529:
530: /**
531: * Writes the URL contents to a file specified by the fileLocation argument.
532: *
533: * @param url the URL to read
534: * @param fileLocation the location which to write.
535: * @param encoding the optional encoding
536: */
537: public static void writeUrlToFile(final URL url,
538: final String fileLocation, final String encoding)
539: throws IOException {
540: ExceptionUtils.checkNull("url", url);
541: ExceptionUtils.checkEmpty("fileLocation", fileLocation);
542: final File file = new File(fileLocation);
543: final File parent = file.getParentFile();
544: if (parent != null) {
545: parent.mkdirs();
546: }
547: OutputStream stream = new BufferedOutputStream(
548: new FileOutputStream(file));
549: if (StringUtils.isNotBlank(encoding)) {
550: BufferedReader inputReader = new BufferedReader(
551: new InputStreamReader(url.openStream(), encoding));
552: for (int ctr = inputReader.read(); ctr != -1; ctr = inputReader
553: .read()) {
554: stream.write(ctr);
555: }
556: inputReader.close();
557: inputReader = null;
558: } else {
559: InputStream inputStream = new BufferedInputStream(url
560: .openStream());
561: for (int ctr = inputStream.read(); ctr != -1; ctr = inputStream
562: .read()) {
563: stream.write(ctr);
564: }
565: inputStream.close();
566: inputStream = null;
567: }
568: stream.flush();
569: stream.close();
570: stream = null;
571: }
572:
573: /**
574: * Indicates whether or not the given <code>url</code> is a file.
575: *
576: * @param url the URL to check.
577: * @return true/false
578: */
579: public static boolean isFile(final URL url) {
580: return url != null && new File(url.getFile()).isFile();
581: }
582:
583: /**
584: * Recursively deletes a directory and its contents.
585: *
586: * @param directory the directory to delete.
587: */
588: public static void deleteDirectory(final File directory) {
589: if (directory != null && directory.exists()
590: && directory.isDirectory()) {
591: final File[] files = directory.listFiles();
592: if (files != null && files.length > 0) {
593: final int mergedTemplatesCount = files.length;
594: for (int ctr = 0; ctr < mergedTemplatesCount; ctr++) {
595: final File file = files[ctr];
596: if (file.isDirectory()) {
597: deleteDirectory(file);
598: file.delete();
599: } else {
600: file.delete();
601: }
602: }
603: }
604: }
605: }
606:
607: /**
608: * The forward slash character.
609: */
610: private static final String FORWARD_SLASH = "/";
611:
612: /**
613: * Gets the contents of this directory and any of its sub directories based on the given <code>patterns</code>.
614: * And returns absolute or relative paths depending on the value of <code>absolute</code>.
615: *
616: * @param url the URL of the directory.
617: * @param absolute whether or not the returned content paths should be absoluate (if
618: * false paths will be relative to URL).
619: * @return a collection of paths.
620: */
621: public static List getDirectoryContents(final URL url,
622: boolean absolute, final String[] patterns) {
623: List contents = ResourceUtils
624: .getDirectoryContents(url, 0, true);
625:
626: // - first see if its a directory
627: if (!contents.isEmpty()) {
628: for (final ListIterator iterator = contents.listIterator(); iterator
629: .hasNext();) {
630: String path = (String) iterator.next();
631: if (!matchesAtLeastOnePattern(path, patterns)) {
632: iterator.remove();
633: } else if (absolute) {
634: path = url.toString().endsWith(FORWARD_SLASH) ? path
635: : FORWARD_SLASH + path;
636: final URL resource = ResourceUtils
637: .toURL(url + path);
638: if (resource != null) {
639: iterator.set(resource.toString());
640: }
641: }
642: }
643: } else // - otherwise handle archives (i.e. jars, etc).
644: {
645: final String urlAsString = url.toString();
646: final String delimiter = "!/";
647: final String archivePath = urlAsString.replaceAll(delimiter
648: + ".*", delimiter);
649: contents = ResourceUtils.getClassPathArchiveContents(url);
650: for (final ListIterator iterator = contents.listIterator(); iterator
651: .hasNext();) {
652: final String relativePath = (String) iterator.next();
653: final String fullPath = archivePath + relativePath;
654: if (!fullPath.startsWith(urlAsString)
655: || fullPath.equals(urlAsString + FORWARD_SLASH)) {
656: iterator.remove();
657: } else if (!matchesAtLeastOnePattern(relativePath,
658: patterns)) {
659: iterator.remove();
660: } else if (absolute) {
661: iterator.set(fullPath);
662: }
663: }
664: }
665: return contents;
666: }
667:
668: /**
669: * Indicates whether or not the given <code>path</code> matches on
670: * one or more of the patterns defined within this class
671: * returns true if no patterns are defined.
672: *
673: * @param path the path to match on.
674: * @return true/false
675: */
676: public static boolean matchesAtLeastOnePattern(final String path,
677: final String[] patterns) {
678: boolean matches = patterns == null || patterns.length == 0;
679: if (!matches) {
680: if (patterns.length > 0) {
681: final int patternNumber = patterns.length;
682: for (int ctr = 0; ctr < patternNumber; ctr++) {
683: final String pattern = patterns[ctr];
684: if (PathMatcher.wildcardMatch(path, pattern)) {
685: matches = true;
686: break;
687: }
688: }
689: }
690: }
691: return matches;
692: }
693:
694: /**
695: * Indicates whether or not the contents of the given <code>directory</code>
696: * and any of its sub directories have been modified after the given <code>time</code>.
697: *
698: * @param directory the directory to check
699: * @param time the time to check against
700: * @return true/false
701: */
702: public static boolean modifiedAfter(long time, final File directory) {
703: final List files = new ArrayList();
704: ResourceUtils.loadFiles(directory, files, true);
705: boolean changed = files.isEmpty();
706: for (final Iterator iterator = files.iterator(); iterator
707: .hasNext();) {
708: final File file = (File) iterator.next();
709: changed = file.lastModified() < time;
710: if (changed) {
711: break;
712: }
713: }
714: return changed;
715: }
716:
717: /**
718: * The pattern used for normalizing paths paths with more than one back slash.
719: */
720: private static final String BACK_SLASH_NORMALIZATION_PATTERN = "\\\\+";
721:
722: /**
723: * The pattern used for normalizing paths with more than one forward slash.
724: */
725: private static final String FORWARD_SLASH_NORMALIZATION_PATTERN = FORWARD_SLASH
726: + "+";
727:
728: /**
729: * Removes any extra path separators and converts all from back slashes
730: * to forward slashes.
731: *
732: * @param path the path to normalize.
733: * @return the normalizd path
734: */
735: public static String normalizePath(final String path) {
736: return path != null ? path.replaceAll(
737: BACK_SLASH_NORMALIZATION_PATTERN, FORWARD_SLASH)
738: .replaceAll(FORWARD_SLASH_NORMALIZATION_PATTERN,
739: FORWARD_SLASH) : null;
740: }
741:
742: /**
743: * Takes a path and replaces the oldException with the newExtension.
744: *
745: * @param path the path to rename.
746: * @param oldExtension the extension to rename from.
747: * @param newExtension the extension to rename to.
748: * @return the path with the new extension.
749: */
750: public static String renameExtension(final String path,
751: final String oldExtension, final String newExtension) {
752: ExceptionUtils.checkEmpty("path", path);
753: ExceptionUtils.checkNull("oldExtension", oldExtension);
754: ExceptionUtils.checkNull("newExtension", newExtension);
755: String newPath = path;
756: final int oldExtensionIndex = path.lastIndexOf(oldExtension);
757: if (oldExtensionIndex != -1) {
758: newPath = path.substring(0, oldExtensionIndex)
759: + newExtension;
760: }
761: return newPath;
762: }
763: }
|