/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.vmutil; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import org.eclipse.xtext.xbase.lib.Inline; import org.eclipse.xtext.xbase.lib.Pure; import org.arakhne.afc.vmutil.locale.Locale; /** An utility class that permits to deal with filenames. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ @SuppressWarnings({"checkstyle:methodcount"}) public final class FileSystem { static { URLHandlerUtil.installArakhneHandlers(); final String validChars = "[^\\\\/:*?\"<>|]"; //$NON-NLS-1$ final String bslashChar = "\\\\"; //$NON-NLS-1$ final StringBuilder pattern = new StringBuilder(); pattern.append("^"); //$NON-NLS-1$ pattern.append("(([a-zA-Z][:|]"); //$NON-NLS-1$ pattern.append(validChars); pattern.append("*)|("); //$NON-NLS-1$ pattern.append(validChars); pattern.append("+"); //$NON-NLS-1$ pattern.append(bslashChar); pattern.append(validChars); pattern.append("+)|("); //$NON-NLS-1$ pattern.append(bslashChar); pattern.append("))?("); //$NON-NLS-1$ pattern.append(bslashChar); pattern.append(validChars); pattern.append("*)*"); //$NON-NLS-1$ pattern.append("$"); //$NON-NLS-1$ //"^([A-Za-z]:)?([^\\\\/:*?\"<>|]*\\\\)*[^\\\\/:*?\"<>|]*$"; WINDOW_NATIVE_FILENAME_PATTERN = pattern.toString(); } /** Character used to specify a file extension. */ public static final char EXTENSION_SEPARATOR_CHAR = '.'; /** String which is representing the current directory in a relative path. */ public static final String CURRENT_DIRECTORY = "."; //$NON-NLS-1$ /** String which is representing the parent directory in a relative path. */ public static final String PARENT_DIRECTORY = ".."; //$NON-NLS-1$ /** Character used to separate paths on an URL. */ public static final char URL_PATH_SEPARATOR_CHAR = '/'; /** Character used to separate paths on an URL. */ public static final String URL_PATH_SEPARATOR = "/"; //$NON-NLS-1$ /** String used to specify a file extension. */ public static final String EXTENSION_SEPARATOR = "."; //$NON-NLS-1$ /** Prefix used to join in a Jar URL the jar filename and the inside-jar filename. */ public static final String JAR_URL_FILE_ROOT = "!/"; //$NON-NLS-1$ /** Character that is used for separating components of a path on Windows operating systems. */ public static final char WINDOWS_SEPARATOR_CHAR = '\\'; /** Character that is used for separating components of a path on Windows operating systems. */ public static final String WINDOWS_SEPARATOR_STRING = "\\"; //$NON-NLS-1$ /** Character that is used for separating components of a path on Unix operating systems. */ public static final String UNIX_SEPARATOR_STRING = URL_PATH_SEPARATOR; /** Regular expression pattern which corresponds to Windows native filename. */ private static final String WINDOW_NATIVE_FILENAME_PATTERN; private static final Random RANDOM = new Random(); private static final DeleteOnExitHook DELETE_ON_EXIT_HOOK = new DeleteOnExitHook(); private static Boolean isFileCompatibleWithURL; private static final int BUFFER_SIZE = 4096; private static final char[] FILE_PREFIX = {'f', 'i', 'l', 'e', ':', '/', '/'}; private FileSystem() { // } /** Replace the HTML entities by the current charset characters. * * @param string the string to decode. * @return decoded string or {@code s}. */ private static String decodeHTMLEntities(String string) { if (string == null) { return null; } try { return URLDecoder.decode(string, Charset.defaultCharset().displayName()); } catch (UnsupportedEncodingException exception) { return string; } } /** Replace the special characters by HTML entities. * * @param string the string to decode. * @return decoded string or {@code s}. */ private static String encodeHTMLEntities(String string) { if (string == null) { return null; } try { return URLEncoder.encode(string, Charset.defaultCharset().displayName()); } catch (UnsupportedEncodingException exception) { return string; } } /** Decode the given file to obtain a string representation * which is compatible with the URL standard. * This function was introduced to have a work around * on the '\' character on Windows operating system. * * @param file the file. * @return the string representation of the file. * @since 6.2 */ private static String fromFileStandardToURLStandard(File file) { if (file == null) { return null; } return fromFileStandardToURLStandard(file.getPath()); } /** Decode the given file to obtain a string representation * which is compatible with the URL standard. * This function was introduced to have a work around * on the '\' character on Windows operating system. * * @param file the file. * @return the string representation of the file. * @since 6.2 */ private static String fromFileStandardToURLStandard(String file) { if (file == null) { return null; } if (isFileCompatibleWithURL == null) { isFileCompatibleWithURL = Boolean.valueOf( URL_PATH_SEPARATOR.equals(File.separator)); } String filePath = file; if (!isFileCompatibleWithURL) { filePath = filePath.replaceAll( Pattern.quote(File.separator), Matcher.quoteReplacement(URL_PATH_SEPARATOR)); } // Add root slash for Windows paths that are starting with a disk id. if (Pattern.matches("^[a-zA-Z][:|].*$", filePath)) { //$NON-NLS-1$ filePath = URL_PATH_SEPARATOR + filePath; } return filePath; } /** Replies if the given URL has a jar scheme. * * @param url the URL to test. * @return <code>true</code> if the given URL uses a jar scheme. */ @Pure @Inline(value = "URISchemeType.JAR.isURL($1)", imported = {URISchemeType.class}) public static boolean isJarURL(URL url) { return URISchemeType.JAR.isURL(url); } /** Replies the jar part of the jar-scheme URL. * * <p>If the input URL is {@code jar:file:/path1/archive.jar!/path2/file}, * the output of this function is {@code file:/path1/archive.jar}. * * @param url the URL to test. * @return the URL of the jar file in the given URL, or <code>null</code> * if the given URL does not use jar scheme. */ @Pure public static URL getJarURL(URL url) { if (!isJarURL(url)) { return null; } String path = url.getPath(); final int idx = path.lastIndexOf(JAR_URL_FILE_ROOT); if (idx >= 0) { path = path.substring(0, idx); } try { return new URL(path); } catch (MalformedURLException exception) { return null; } } /** Replies the file part of the jar-scheme URL. * * <p>If the input URL is {@code jar:file:/path1/archive.jar!/path2/file}, * the output of this function is {@code /path2/file}. * * @param url the URL to test. * @return the file in the given URL, or <code>null</code> * if the given URL does not use jar scheme. */ @Pure public static File getJarFile(URL url) { if (isJarURL(url)) { final String path = url.getPath(); final int idx = path.lastIndexOf(JAR_URL_FILE_ROOT); if (idx >= 0) { return new File(decodeHTMLEntities(path.substring(idx + 1))); } } return null; } /** Replies the jar-schemed URL composed of the two given components. * * <p>If the inputs are {@code /path1/archive.jar} and @{code /path2/file}, * the output of this function is {@code jar:file:/path1/archive.jar!/path2/file}. * * @param jarFile is the URL to the jar file. * @param insideFile is the name of the file inside the jar. * @return the jar-schemed URL. * @throws MalformedURLException when the URL is malformed. */ @Pure public static URL toJarURL(File jarFile, File insideFile) throws MalformedURLException { if (jarFile == null || insideFile == null) { return null; } return toJarURL(jarFile, fromFileStandardToURLStandard(insideFile)); } /** Replies the jar-schemed URL composed of the two given components. * * <p>If the inputs are {@code /path1/archive.jar} and @{code /path2/file}, * the output of this function is {@code jar:file:/path1/archive.jar!/path2/file}. * * @param jarFile is the URL to the jar file. * @param insideFile is the name of the file inside the jar. * @return the jar-schemed URL. * @throws MalformedURLException when the URL is malformed. */ @Pure @Inline("toJarURL(($1).toURI().toURL(), ($2))") public static URL toJarURL(File jarFile, String insideFile) throws MalformedURLException { return toJarURL(jarFile.toURI().toURL(), insideFile); } /** Replies the jar-schemed URL composed of the two given components. * * <p>If the inputs are {@code file:/path1/archive.jar} and @{code /path2/file}, * the output of this function is {@code jar:file:/path1/archive.jar!/path2/file}. * * @param jarFile is the URL to the jar file. * @param insideFile is the name of the file inside the jar. * @return the jar-schemed URL. * @throws MalformedURLException when the URL is malformed. */ @Pure public static URL toJarURL(URL jarFile, File insideFile) throws MalformedURLException { if (jarFile == null || insideFile == null) { return null; } return toJarURL(jarFile, fromFileStandardToURLStandard(insideFile)); } /** Replies the jar-schemed URL composed of the two given components. * * <p>If the inputs are {@code file:/path1/archive.jar} and @{code /path2/file}, * the output of this function is {@code jar:file:/path1/archive.jar!/path2/file}. * * @param jarFile is the URL to the jar file. * @param insideFile is the name of the file inside the jar. * @return the jar-schemed URL. * @throws MalformedURLException when the URL is malformed. */ @Pure public static URL toJarURL(URL jarFile, String insideFile) throws MalformedURLException { if (jarFile == null || insideFile == null) { return null; } final StringBuilder buf = new StringBuilder(); buf.append("jar:"); //$NON-NLS-1$ buf.append(jarFile.toExternalForm()); buf.append(JAR_URL_FILE_ROOT); final String path = fromFileStandardToURLStandard(insideFile); if (path.startsWith(URL_PATH_SEPARATOR)) { buf.append(path.substring(URL_PATH_SEPARATOR.length())); } else { buf.append(path); } return new URL(buf.toString()); } /** Replies if the current operating system uses case-sensitive filename. * * @return <code>true</code> if the filenames on the current file system are case sensitive, * otherwise <code>false</code> */ @Pure public static boolean isCaseSensitiveFilenameSystem() { switch (OperatingSystem.getCurrentOS()) { case AIX: case ANDROID: case BSD: case FREEBSD: case NETBSD: case OPENBSD: case LINUX: case SOLARIS: case HPUX: return true; case MACOSX: case WIN: case OTHER: default: return false; } } /** Replies the character used to separate the basename and the file extension. * * @return the character used to separate the basename and the file extension. */ @Pure @Inline(value = "EXTENSION_SEPARATOR_CHAR", constantExpression = true) public static char getFileExtensionCharacter() { return EXTENSION_SEPARATOR_CHAR; } /** Replies the dirname of the specified file. * * @param filename is the name to parse. * @return the dirname of the specified file. * @see #shortBasename(File) * @see #largeBasename(File) * @see #basename(File) * @see #extension(File) */ @Pure public static URL dirname(File filename) { if (filename == null) { return null; } String parent = fromFileStandardToURLStandard(filename.getParent()); try { if (parent == null || "".equals(parent)) { //$NON-NLS-1$ if (filename.isAbsolute()) { return null; } return new URL(URISchemeType.FILE.name(), "", CURRENT_DIRECTORY); //$NON-NLS-1$ } // Treat Windows specific. if (Pattern.matches("^" + URL_PATH_SEPARATOR + "?[a-zA-Z][:|]$", parent)) { //$NON-NLS-1$ //$NON-NLS-2$ parent += URL_PATH_SEPARATOR; } return new URL(URISchemeType.FILE.name(), "", parent); //$NON-NLS-1$ } catch (MalformedURLException exception) { return null; } } /** Replies the dirname of the specified file. * * @param filename is the name to parse. * @return the dirname of the specified file. * @see #shortBasename(URL) * @see #largeBasename(URL) * @see #basename(URL) * @see #extension(URL) */ @Pure @SuppressWarnings("checkstyle:npathcomplexity") public static URL dirname(URL filename) { if (filename == null) { return null; } URL prefix = null; String path; if (isJarURL(filename)) { prefix = getJarURL(filename); path = fromFileStandardToURLStandard(getJarFile(filename)); } else { path = filename.getPath(); } if ("".equals(path)) { //$NON-NLS-1$ return null; } int idx = path.lastIndexOf(URL_PATH_SEPARATOR_CHAR); if (idx == path.length() - 1) { idx = path.lastIndexOf(URL_PATH_SEPARATOR_CHAR, path.length() - 2); } if (idx < 0) { if (URISchemeType.getSchemeType(filename).isFileBasedScheme()) { path = CURRENT_DIRECTORY; } else { path = URL_PATH_SEPARATOR; } } else { path = path.substring(0, idx + 1); } try { if (prefix != null) { return toJarURL(prefix, path); } return new URI(filename.getProtocol(), filename.getUserInfo(), filename.getHost(), filename.getPort(), decodeHTMLEntities(path), null, null).toURL(); } catch (Throwable exception) { // } try { return new URL( filename.getProtocol(), filename.getHost(), path); } catch (Throwable exception) { // } return null; } /** Replies the basename of the specified file with the extension. * * <p>Caution: This function does not support URL format. * * @param filename is the name to parse. * @return the basename of the specified file with the extension. */ @Pure public static String largeBasename(String filename) { if (filename == null) { return null; } if (isWindowsNativeFilename(filename)) { return largeBasename(normalizeWindowsNativeFilename(filename)); } try { return largeBasename(new URL(filename)); } catch (Exception exception) { // No log } int end = filename.length(); int idx; do { end--; idx = filename.lastIndexOf(File.separatorChar, end); } while (idx >= 0 && end >= 0 && idx >= end); if (idx < 0) { if (end < filename.length() - 1) { return filename.substring(0, end + 1); } return filename; } return filename.substring(idx + 1, end + 1); } /** Replies the basename of the specified file with the extension. * * @param filename is the name to parse. * @return the basename of the specified file with the extension. */ @Pure @Inline("((($1) == null) ? null : ($1).getName())") public static String largeBasename(File filename) { if (filename == null) { return null; } return filename.getName(); } /** Replies the basename of the specified file with the extension. * * @param filename is the name to parse. * @return the basename of the specified file with the extension. */ @Pure public static String largeBasename(URL filename) { if (filename == null) { return null; } final String fullPath = filename.getPath(); assert !isWindowsNativeFilename(fullPath); int idx; int end = fullPath.length(); do { end--; idx = fullPath.lastIndexOf(URL_PATH_SEPARATOR_CHAR, end); } while (idx >= 0 && end >= 0 && idx >= end); final String result; if (idx < 0) { if (end < fullPath.length() - 1) { result = fullPath.substring(0, end + 1); } else { result = fullPath; } } else { result = fullPath.substring(idx + 1, end + 1); } return decodeHTMLEntities(result); } /** Reply the basename of the specified file without the last extension. * * <p>Caution: This function does not support URL format. * * @param filename is the name to parse. * @return the basename of the specified file without the last extension. * @see #shortBasename(String) * @see #largeBasename(String) */ @Pure public static String basename(String filename) { if (filename == null) { return null; } if (isWindowsNativeFilename(filename)) { return basename(normalizeWindowsNativeFilename(filename)); } try { return basename(new URL(filename)); } catch (Exception exception) { // No log } int end = filename.length(); int idx; do { end--; idx = filename.lastIndexOf(File.separatorChar, end); } while (idx >= 0 && end >= 0 && idx >= end); final String basename; if (idx < 0) { if (end < filename.length() - 1) { basename = filename.substring(0, end + 1); } else { basename = filename; } } else { basename = filename.substring(idx + 1, end + 1); } idx = basename.lastIndexOf(getFileExtensionCharacter()); if (idx < 0) { return basename; } return basename.substring(0, idx); } /** Reply the basename of the specified file without the last extension. * * @param filename is the name to parse. * @return the basename of the specified file without the last extension. * @see #shortBasename(File) * @see #largeBasename(File) * @see #dirname(File) * @see #extension(File) */ @Pure public static String basename(File filename) { if (filename == null) { return null; } final String largeBasename = filename.getName(); final int idx = largeBasename.lastIndexOf(getFileExtensionCharacter()); if (idx <= 0) { return largeBasename; } return largeBasename.substring(0, idx); } /** Reply the basename of the specified file without the last extension. * * @param filename is the name to parse. * @return the basename of the specified file without the last extension. * @see #shortBasename(URL) * @see #largeBasename(URL) * @see #dirname(URL) * @see #extension(URL) */ @Pure public static String basename(URL filename) { if (filename == null) { return null; } final String largeBasename = filename.getPath(); assert !isWindowsNativeFilename(largeBasename); int end = largeBasename.length(); int idx; do { end--; idx = largeBasename.lastIndexOf(URL_PATH_SEPARATOR_CHAR, end); } while (idx >= 0 && end >= 0 && idx >= end); String basename; if (idx < 0) { if (end < largeBasename.length() - 1) { basename = largeBasename.substring(0, end + 1); } else { basename = largeBasename; } } else { basename = largeBasename.substring(idx + 1, end + 1); } idx = basename.lastIndexOf(getFileExtensionCharacter()); if (idx >= 0) { basename = basename.substring(0, idx); } return decodeHTMLEntities(basename); } /** Reply the basename of the specified file without all the extensions. * * <p>Caution: This function does not support URL format. * * @param filename is the name to parse. * @return the basename of the specified file without all the extensions. */ @Pure public static String shortBasename(String filename) { if (filename == null) { return null; } if (isWindowsNativeFilename(filename)) { return shortBasename(normalizeWindowsNativeFilename(filename)); } try { return shortBasename(new URL(filename)); } catch (Exception exception) { // No log } final String normalizedFilename = fromFileStandardToURLStandard(filename); int idx; int end = normalizedFilename.length(); do { end--; idx = normalizedFilename.lastIndexOf(URL_PATH_SEPARATOR_CHAR, end); } while (idx >= 0 && end >= 0 && idx >= end); final String basename; if (idx < 0) { if (end < normalizedFilename.length() - 1) { basename = normalizedFilename.substring(0, end + 1); } else { basename = normalizedFilename; } } else { basename = normalizedFilename.substring(idx + 1, end + 1); } idx = basename.indexOf(getFileExtensionCharacter()); if (idx < 0) { return basename; } return basename.substring(0, idx); } /** Reply the basename of the specified file without all the extensions. * * @param filename is the name to parse. * @return the basename of the specified file without all the extensions. */ @Pure public static String shortBasename(File filename) { if (filename == null) { return null; } final String largeBasename = filename.getName(); final int idx = largeBasename.indexOf(getFileExtensionCharacter()); if (idx < 0) { return largeBasename; } return largeBasename.substring(0, idx); } /** Reply the basename of the specified file without all the extensions. * * @param filename is the name to parse. * @return the basename of the specified file without all the extensions. */ @Pure public static String shortBasename(URL filename) { if (filename == null) { return null; } final String largeBasename = filename.getPath(); assert !isWindowsNativeFilename(largeBasename); int idx; int end = largeBasename.length(); do { end--; idx = largeBasename.lastIndexOf(URL_PATH_SEPARATOR_CHAR, end); } while (idx >= 0 && end >= 0 && idx >= end); String basename; if (idx < 0) { if (end < largeBasename.length() - 1) { basename = largeBasename.substring(0, end + 1); } else { basename = largeBasename; } } else { basename = largeBasename.substring(idx + 1, end + 1); } idx = basename.indexOf(getFileExtensionCharacter()); if (idx >= 0) { basename = basename.substring(0, idx); } return decodeHTMLEntities(basename); } /** Reply the extension of the specified file. * * @param filename is the name to parse. * @return the extension of the specified file * @see #shortBasename(File) * @see #largeBasename(File) * @see #basename(File) * @see #dirname(File) * @see #extensions(File) */ @Pure public static String extension(File filename) { if (filename == null) { return null; } final String largeBasename = largeBasename(filename); final int idx = largeBasename.lastIndexOf(getFileExtensionCharacter()); if (idx <= 0) { return ""; //$NON-NLS-1$ } return largeBasename.substring(idx); } /** Reply the extension of the specified file. * * @param filename is the name to parse. * @return the extension of the specified file * @since 7.0 * @see #shortBasename(File) * @see #largeBasename(File) * @see #basename(File) * @see #dirname(File) * @see #extensions(File) */ @Pure public static String extension(String filename) { if (filename == null) { return null; } final String largeBasename = largeBasename(filename); final int idx = largeBasename.lastIndexOf(getFileExtensionCharacter()); if (idx <= 0) { return ""; //$NON-NLS-1$ } return largeBasename.substring(idx); } /** Reply the extension of the specified file. * * @param filename is the name to parse. * @return the extension of the specified file * @see #shortBasename(URL) * @see #largeBasename(URL) * @see #basename(URL) * @see #dirname(URL) * @see #extensions(URL) */ @Pure public static String extension(URL filename) { if (filename == null) { return null; } final String largeBasename = largeBasename(filename); final int idx = largeBasename.lastIndexOf(getFileExtensionCharacter()); if (idx <= 0) { return ""; //$NON-NLS-1$ } return decodeHTMLEntities(largeBasename.substring(idx)); } /** Reply all the extensions of the specified file. * * @param filename is the name to parse. * @return the extensions of the specified file */ @Pure public static String[] extensions(File filename) { if (filename == null) { return new String[0]; } final String largeBasename = largeBasename(filename); final String[] parts = largeBasename.split(Pattern.quote(Character.toString(getFileExtensionCharacter()))); if (parts.length <= 1) { return new String[0]; } final String[] result = new String[parts.length - 1]; System.arraycopy(parts, 1, result, 0, result.length); return result; } /** Reply all the extensions of the specified file. * * @param filename is the name to parse. * @return the extensions of the specified file * @since 7.0 */ @Pure public static String[] extensions(String filename) { if (filename == null) { return new String[0]; } final String largeBasename = largeBasename(filename); final String[] parts = largeBasename.split(Pattern.quote(Character.toString(getFileExtensionCharacter()))); if (parts.length <= 1) { return new String[0]; } final String[] result = new String[parts.length - 1]; System.arraycopy(parts, 1, result, 0, result.length); return result; } /** Reply all the extensions of the specified file. * * @param filename is the name to parse. * @return the extensions of the specified file */ @Pure public static String[] extensions(URL filename) { if (filename == null) { return new String[0]; } final String largeBasename = largeBasename(filename); final String[] parts = largeBasename.split(Pattern.quote(Character.toString(getFileExtensionCharacter()))); if (parts.length <= 1) { return new String[0]; } final String[] result = new String[parts.length - 1]; for (int i = 0; i < result.length; ++i) { result[i] = decodeHTMLEntities(parts[i + 1]); } return result; } /** Replies the parts of a path. * * @param filename is the name to parse. * @return the parts of a path. */ @Pure public static String[] split(File filename) { if (filename == null) { return new String[0]; } return filename.getPath().split(Pattern.quote(File.separator)); } /** Replies the parts of a path. * * <p>If the input is {@code "http://www.arakhne.org/path/to/file.x.z.z"}, the replied paths * are: {@code ""}, {@code "path"}, {@code "to"}, and {@code "file.x.z.z"}. * * <p>If the input is {@code "jar:file:/path1/archive.jar!/path2/file"}, the replied paths * are: {@code ""}, {@code "path2"}, and {@code "file"}. * * @param filename is the name to parse. * @return the parts of a path. */ @Pure public static String[] split(URL filename) { if (filename == null) { return new String[0]; } if (isJarURL(filename)) { return split(getJarFile(filename)); } final String path = filename.getPath(); String[] tab = path.split(Pattern.quote(URL_PATH_SEPARATOR)); if (tab.length >= 2 && "".equals(tab[0]) && Pattern.matches("^[a-zA-Z][:|]$", tab[1])) { //$NON-NLS-1$ //$NON-NLS-2$ tab = Arrays.copyOfRange(tab, 1, tab.length); for (int i = 1; i < tab.length; ++i) { tab[i] = decodeHTMLEntities(tab[i]); } } else { for (int i = 0; i < tab.length; ++i) { tab[i] = decodeHTMLEntities(tab[i]); } } return tab; } /** Join the parts of a path and append them to the given File. * * @param fileBase is the file to put as prefix. * @param elements are the path's elements to join. * @return the result of the join of the path's elements. */ @Pure public static File join(File fileBase, String... elements) { if (fileBase == null) { return null; } final StringBuilder buf = new StringBuilder(fileBase.getPath()); boolean empty; for (final String elt : elements) { empty = elt == null || elt.length() == 0; if (!empty) { assert elt != null; if (!elt.startsWith(File.separator) && buf.length() >= 0 && buf.charAt(buf.length() - 1) != File.separatorChar) { buf.append(File.separatorChar); } buf.append(elt); } } return new File(buf.toString()); } /** Join the parts of a path and append them to the given File. * * @param fileBase is the file to put as prefix. * @param elements are the path's elements to join. * @return the result of the join of the path's elements. */ @Pure public static File join(File fileBase, File... elements) { if (fileBase == null) { return null; } final StringBuilder buf = new StringBuilder(fileBase.getPath()); for (final File elt : elements) { if (!elt.isAbsolute() && buf.length() >= 0 && buf.charAt(buf.length() - 1) != File.separatorChar) { buf.append(File.separatorChar); } buf.append(elt.getPath()); } return new File(buf.toString()); } /** Join the parts of a path and append them to the given URL. * * @param urlBase is the url to put as prefix. * @param elements are the path's elements to join. * @return the result of the join of the path's elements. */ @Pure public static URL join(URL urlBase, String... elements) { if (urlBase == null) { return null; } final StringBuilder buf = new StringBuilder(decodeHTMLEntities(urlBase.getPath().replaceFirst( Pattern.quote(URL_PATH_SEPARATOR) + "+$", ""))); //$NON-NLS-1$//$NON-NLS-2$ boolean empty; for (final String elt : elements) { empty = elt == null || elt.length() == 0; if (!empty) { assert elt != null; if (!elt.startsWith(File.separator) && (buf.length() == 0 || buf.charAt(buf.length() - 1) != URL_PATH_SEPARATOR_CHAR)) { buf.append(URL_PATH_SEPARATOR_CHAR); } buf.append(elt); } } try { if (isJarURL(urlBase)) { return new URL( urlBase.getProtocol(), urlBase.getHost(), urlBase.getPort(), buf.toString()); } return new URI( urlBase.getProtocol(), urlBase.getUserInfo(), urlBase.getHost(), urlBase.getPort(), buf.toString(), decodeHTMLEntities(urlBase.getQuery()), urlBase.getRef()).toURL(); } catch (Throwable exception) { // } try { return new URL( urlBase.getProtocol(), urlBase.getHost(), buf.toString()); } catch (Throwable exception) { return null; } } /** Join the parts of a path and append them to the given URL. * * @param urlBase is the url to put as prefix. * @param elements are the path's elements to join. * @return the result of the join of the path's elements. */ @Pure public static URL join(URL urlBase, File... elements) { if (urlBase == null) { return null; } final StringBuilder buf = new StringBuilder(urlBase.getPath()); for (final File elt : elements) { if (!elt.isAbsolute() && (buf.length() == 0 || buf.charAt(buf.length() - 1) != URL_PATH_SEPARATOR_CHAR)) { buf.append(URL_PATH_SEPARATOR_CHAR); } buf.append(fromFileStandardToURLStandard(elt)); } try { if (isJarURL(urlBase)) { return new URL( urlBase.getProtocol(), urlBase.getHost(), urlBase.getPort(), buf.toString()); } return new URI( urlBase.getProtocol(), urlBase.getUserInfo(), urlBase.getHost(), urlBase.getPort(), decodeHTMLEntities(buf.toString()), decodeHTMLEntities(urlBase.getQuery()), urlBase.getRef()).toURL(); } catch (Throwable exception) { // } try { return new URL( urlBase.getProtocol(), urlBase.getHost(), buf.toString()); } catch (Throwable exception) { return null; } } /** Replies if the specified file has the specified extension. * * <p>The test is dependent of the case-sensitive attribute of operating system. * * @param filename is the filename to parse * @param extension is the extension to test. * @return <code>true</code> if the given filename has the given extension, * otherwise <code>false</code> */ @Pure public static boolean hasExtension(File filename, String extension) { if (filename == null) { return false; } assert extension != null; String extent = extension; if (!"".equals(extent) && !extent.startsWith(EXTENSION_SEPARATOR)) { //$NON-NLS-1$ extent = EXTENSION_SEPARATOR + extent; } final String ext = extension(filename); if (ext == null) { return false; } if (isCaseSensitiveFilenameSystem()) { return ext.equals(extent); } return ext.equalsIgnoreCase(extent); } /** Replies if the specified file has the specified extension. * * <p>The test is dependent of the case-sensitive attribute of operating system. * * @param filename is the filename to parse * @param extension is the extension to test. * @return <code>true</code> if the given filename has the given extension, * otherwise <code>false</code> * @since 7.0 */ @Pure public static boolean hasExtension(String filename, String extension) { if (filename == null) { return false; } assert extension != null; String extent = extension; if (!"".equals(extent) && !extent.startsWith(EXTENSION_SEPARATOR)) { //$NON-NLS-1$ extent = EXTENSION_SEPARATOR + extent; } final String ext = extension(filename); if (ext == null) { return false; } if (isCaseSensitiveFilenameSystem()) { return ext.equals(extent); } return ext.equalsIgnoreCase(extent); } /** Replies if the specified file has the specified extension. * * <p>The test is dependent of the case-sensitive attribute of operating system. * * @param filename is the filename to parse * @param extension is the extension to test. * @return <code>true</code> if the given filename has the given extension, * otherwise <code>false</code> */ @Pure public static boolean hasExtension(URL filename, String extension) { if (filename == null) { return false; } assert extension != null; String extent = extension; if (!"".equals(extent) && !extent.startsWith(EXTENSION_SEPARATOR)) { //$NON-NLS-1$ extent = EXTENSION_SEPARATOR + extent; } final String ext = extension(filename); if (ext == null) { return false; } if (isCaseSensitiveFilenameSystem()) { return ext.equals(extent); } return ext.equalsIgnoreCase(extent); } /** Remove the extension from the specified filename. * * @param filename is the filename to parse. * @return the filename without the extension. */ @Pure public static File removeExtension(File filename) { if (filename == null) { return null; } final File dir = filename.getParentFile(); final String name = filename.getName(); final int idx = name.lastIndexOf(getFileExtensionCharacter()); if (idx < 0) { return filename; } return new File(dir, name.substring(0, idx)); } /** Remove the extension from the specified filename. * * @param filename is the filename to parse. * @return the filename without the extension. */ @Pure public static URL removeExtension(URL filename) { if (filename == null) { return null; } final String path = filename.getPath().replaceFirst(Pattern.quote(URL_PATH_SEPARATOR) + "+$", ""); //$NON-NLS-1$ //$NON-NLS-2$ int idx = path.lastIndexOf(URL_PATH_SEPARATOR); final StringBuilder buf = new StringBuilder((idx < 0) ? "" : //$NON-NLS-1$ decodeHTMLEntities(path.substring(0, idx + 1))); final String largeBasename = decodeHTMLEntities(path.substring(idx + 1)); idx = largeBasename.lastIndexOf(getFileExtensionCharacter()); if (idx < 0) { return filename; } buf.append(largeBasename.substring(0, idx)); try { if (isJarURL(filename)) { return new URL( filename.getProtocol(), filename.getHost(), filename.getPort(), buf.toString()); } return new URI( filename.getProtocol(), filename.getUserInfo(), filename.getHost(), filename.getPort(), buf.toString(), decodeHTMLEntities(filename.getQuery()), filename.getRef()).toURL(); } catch (AssertionError e) { throw e; } catch (Throwable exception) { // } try { return new URL( filename.getProtocol(), filename.getHost(), buf.toString()); } catch (AssertionError e) { throw e; } catch (Throwable exception) { return null; } } /** Replace the extension of the specified filename by the given extension. * If the filename has no extension, the specified one will be added. * * @param filename is the filename to parse. * @param extension is the extension to remove if it is existing. * @return the filename without the extension. */ @Pure public static File replaceExtension(File filename, String extension) { if (filename == null) { return null; } if (extension == null) { return filename; } final File dir = filename.getParentFile(); final String name = filename.getName(); final int idx = name.lastIndexOf(getFileExtensionCharacter()); final StringBuilder n = new StringBuilder(); if (idx < 0) { n.append(name); } else { n.append(name.substring(0, idx)); } if (!name.endsWith(EXTENSION_SEPARATOR) && !extension.startsWith(EXTENSION_SEPARATOR)) { n.append(EXTENSION_SEPARATOR); } n.append(extension); return new File(dir, n.toString()); } /** Replace the extension of the specified filename by the given extension. * If the filename has no extension, the specified one will be added. * * @param filename is the filename to parse. * @param extension is the extension to remove if it is existing. * @return the filename without the extension. */ @Pure @SuppressWarnings("checkstyle:npathcomplexity") public static URL replaceExtension(URL filename, String extension) { if (filename == null) { return null; } if (extension == null) { return filename; } String path = filename.getPath().replaceFirst(Pattern.quote(URL_PATH_SEPARATOR) + "+$", ""); //$NON-NLS-1$ //$NON-NLS-2$ if (!path.isEmpty()) { int idx = path.lastIndexOf(URL_PATH_SEPARATOR); final StringBuilder buf = new StringBuilder((idx < 0) ? "" : //$NON-NLS-1$ decodeHTMLEntities(path.substring(0, idx + 1))); final String largeBasename = decodeHTMLEntities(path.substring(idx + 1)); idx = largeBasename.lastIndexOf(getFileExtensionCharacter()); if (idx < 0) { buf.append(largeBasename); } else { buf.append(largeBasename.substring(0, idx)); } if (!"".equals(extension) && !extension.startsWith(EXTENSION_SEPARATOR)) { //$NON-NLS-1$ buf.append(EXTENSION_SEPARATOR); } buf.append(extension); path = buf.toString(); } try { if (isJarURL(filename)) { return new URL( filename.getProtocol(), filename.getHost(), filename.getPort(), path); } return new URI( filename.getProtocol(), filename.getUserInfo(), filename.getHost(), filename.getPort(), path, encodeHTMLEntities(filename.getQuery()), filename.getRef()).toURL(); } catch (AssertionError e) { throw e; } catch (Throwable exception) { // } try { return new URL( filename.getProtocol(), filename.getHost(), path); } catch (AssertionError e) { throw e; } catch (Throwable exception) { return null; } } /** Add the extension of to specified filename. * If the filename has already the given extension, the filename is not changed. * If the filename has no extension or an other extension, the specified one is added. * * @param filename is the filename to parse. * @param extension is the extension to remove if it is existing. * @return the filename with the extension. * @since 6.0 */ @Pure public static File addExtension(File filename, String extension) { if (filename != null && !hasExtension(filename, extension)) { String extent = extension; if (!"".equals(extent) && !extent.startsWith(EXTENSION_SEPARATOR)) { //$NON-NLS-1$ extent = EXTENSION_SEPARATOR + extent; } return new File(filename.getParentFile(), filename.getName() + extent); } return filename; } /** Add the extension of to specified filename. * If the filename has already the given extension, the filename is not changed. * If the filename has no extension or an other extension, the specified one is added. * * @param filename is the filename to parse. * @param extension is the extension to remove if it is existing. * @return the filename with the extension. * @since 6.0 */ @Pure public static URL addExtension(URL filename, String extension) { if (filename != null && !hasExtension(filename, extension)) { final String basename = largeBasename(filename); if (!basename.isEmpty()) { final StringBuilder buf = new StringBuilder(decodeHTMLEntities( filename.getPath()).replaceFirst(Pattern.quote(URL_PATH_SEPARATOR) + "+$", "")); //$NON-NLS-1$ //$NON-NLS-2$ if (!"".equals(extension) && !extension.startsWith(EXTENSION_SEPARATOR)) { //$NON-NLS-1$ buf.append(EXTENSION_SEPARATOR); } buf.append(extension); final String path = buf.toString(); try { if (isJarURL(filename)) { return new URL( filename.getProtocol(), filename.getHost(), filename.getPort(), path); } return new URI( filename.getProtocol(), filename.getUserInfo(), filename.getHost(), filename.getPort(), path, encodeHTMLEntities(filename.getQuery()), filename.getRef()).toURL(); } catch (AssertionError e) { throw e; } catch (Throwable exception) { // } try { return new URL( filename.getProtocol(), filename.getHost(), path); } catch (AssertionError e) { throw e; } catch (Throwable exception) { return null; } } } return filename; } /** Delete the given directory and all its subdirectories. * If the given {@code file} is a directory, its * content and the {@code file} itself are recursivelly removed. * * @param file is the file to delete. * @throws IOException when IO error occurs * @since 6.0 * @see File#delete() for the deletion on a file only. * @see File#mkdir() to create a directory. * @see File#mkdirs() to create a directory and all its parents. */ public static void delete(File file) throws IOException { if (file != null) { final LinkedList<File> candidates = new LinkedList<>(); candidates.add(file); File fl; File[] children; while (!candidates.isEmpty()) { fl = candidates.getFirst(); if (fl.isDirectory()) { children = fl.listFiles(); if (children != null && children.length > 0) { // Non empty directory for (final File c : children) { candidates.push(c); } } else { // empty directory candidates.removeFirst(); fl.delete(); } } else { // not a directory candidates.removeFirst(); fl.delete(); } } } } /** Delete the given directory and all its subdirectories when the JVM is exiting. * If the given {@code file} is a directory, its * content and the {@code file} itself are recursivelly removed. * * <p>To cancel this action, see {@link #undeleteOnExit(File)}. * * @param file is the file to delete. * @throws IOException when cannot register the hook. * @since 6.0 * @see File#deleteOnExit() for the deletion on a file only. * @see File#mkdir() to create a directory. * @see File#mkdirs() to create a directory and all its parents. */ public static void deleteOnExit(File file) throws IOException { if (file != null) { DELETE_ON_EXIT_HOOK.add(file); } } /** Cancel the deletion of the given directory and all its subdirectories when the JVM is exiting. * * @param file is the file to undelete. * @throws IOException when the file cannot be registered. * @since 6.0 * @see #deleteOnExit(File) * @see File#deleteOnExit() for the deletion on a file only. * @see File#mkdir() to create a directory. * @see File#mkdirs() to create a directory and all its parents. */ public static void undeleteOnExit(File file) throws IOException { if (file != null) { DELETE_ON_EXIT_HOOK.remove(file); } } /** Copy the first file into the second file. * * <p>The content of the second file will be lost. * This copy function allows to do a copy between two different * partitions. * * <p>If the {@code out} parameter is a directory, the output file * is a file with the same basename as the input and inside * the {@code out} directory. * * @param in is the file to copy. * @param out is the target file * @throws IOException in case of error. * @since 6.0 * @see #copy(URL, File) */ public static void copy(File in, File out) throws IOException { assert in != null; assert out != null; File outFile = out; if (out.isDirectory()) { outFile = new File(out, largeBasename(in)); } try (FileInputStream fis = new FileInputStream(in)) { try (FileOutputStream fos = new FileOutputStream(outFile)) { copy(fis, (int) in.length(), fos); } } } /** Copy the first file into the second file. * * <p>The content of the second file will be lost. * This copy function allows to do a copy between two different * partitions. * * @param in is the file to copy. * @param out is the target file * @throws IOException in case of error. * @since 6.0 * @see #copy(File, File) */ public static void copy(URL in, File out) throws IOException { assert in != null; assert out != null; File outFile = out; if (out.isDirectory()) { outFile = new File(out, largeBasename(in)); } final URLConnection connection = in.openConnection(); try (FileOutputStream fos = new FileOutputStream(outFile)) { copy( connection.getInputStream(), connection.getContentLength(), fos); } } /** Copy the first file into the second file. * * <p>The content of the second file will be lost. * This copy function allows to do a copy between two different * partitions. * * @param in is the input stream to read. * @param inSize is the total size of the input stream. * @param out is the output stream. * @throws IOException when copy error occurs. * @since 6.2 */ @SuppressWarnings("checkstyle:magicnumber") public static void copy(InputStream in, int inSize, FileOutputStream out) throws IOException { assert in != null; assert out != null; try (ReadableByteChannel inChannel = Channels.newChannel(in)) { try (FileChannel outChannel = out.getChannel()) { // apparently has trouble copying large files on Windows if (inSize < 0 || OperatingSystem.WIN.isCurrentOS()) { // magic number for Windows, 64Mb - 32Kb final int maxCount = (64 * 1024 * 1024) - (32 * 1024); long position = 0; long copied = 1; while ((inSize >= 0 && position < inSize) || (inSize < 0 && copied > 0)) { copied = outChannel.transferFrom(inChannel, position, maxCount); position += copied; } } else { outChannel.transferFrom(inChannel, 0, inSize); } } } } /** Replies the user home directory. * * @return the home directory of the current user. * @throws FileNotFoundException when the home directory does not exist. */ @Pure public static File getUserHomeDirectory() throws FileNotFoundException { final String userHome = System.getProperty("user.home"); //$NON-NLS-1$ if (userHome != null && !userHome.isEmpty()) { final File file = new File(userHome); if (file.isDirectory()) { return file; } } if (OperatingSystem.ANDROID.isCurrentOS()) { return join(File.listRoots()[0], Android.HOME_DIRECTORY); } throw new FileNotFoundException(); } /** Replies the user home directory. * * @return the home directory of the current user. */ @Pure public static String getUserHomeDirectoryName() { final String userHome = System.getProperty("user.home"); //$NON-NLS-1$ if ((userHome == null || userHome.isEmpty()) && (OperatingSystem.ANDROID.isCurrentOS())) { return join(File.listRoots()[0], Android.HOME_DIRECTORY).toString(); } return userHome; } /** Replies the user configuration directory for the specified software. * * <p>On Unix operating systems, the user directory for a * software is by default {@code $HOME/.software} where {@code software} * is the given parameter (case-sensitive). On Windows® operating systems, the user * directory for a software is by default * {@code C:<span>\</span>Documents and Settings<span>\</span>userName<span>\</span>Local Settings<span>\</span> * Application Data<span>\</span>software} * where {@code userName} is the login of the current user and {@code software} * is the given parameter (case-insensitive). * * @param software is the name of the concerned software. * @return the configuration directory of the software for the current user. */ @Pure public static File getUserConfigurationDirectoryFor(String software) { if (software == null || "".equals(software)) { //$NON-NLS-1$ throw new IllegalArgumentException(); } try { final File userHome = getUserHomeDirectory(); final OperatingSystem os = OperatingSystem.getCurrentOS(); if (os == OperatingSystem.ANDROID) { return join(userHome, "Android", Android.DATA_DIRECTORY, //$NON-NLS-1$ Android.makeAndroidApplicationName(software)); } else if (os.isUnixCompliant()) { return new File(new File(userHome, ".config"), software); //$NON-NLS-1$ } else if (os == OperatingSystem.WIN) { final String userName = System.getProperty("user.name"); //$NON-NLS-1$ if (userName != null && !"".equals(userName)) { //$NON-NLS-1$ return join( new File("C:"), //$NON-NLS-1$ "Documents and Settings", //$NON-NLS-1$ userName, "Local Settings", "Application Data", //$NON-NLS-1$ //$NON-NLS-2$ software); } } return new File(userHome, software); } catch (FileNotFoundException exception) { // } return null; } /** Replies the user configuration directory for the specified software. * * <p>On Unix operating systems, the user directory for a * software is by default {@code $HOME/.software} where {@code software} * is the given parameter (case-sensitive). On Windows® operating systems, the user * directory for a software is by default * {@code C:<span>\</span>Documents and Settings<span>\</span>userName<span>\</span>Local Settings<span>\</span> * Application Data<span>\</span>software} * where {@code userName} is the login of the current user and {@code software} * is the given parameter (case-insensitive). * * @param software is the name of the concerned software. * @return the configuration directory of the software for the current user. */ @Pure public static String getUserConfigurationDirectoryNameFor(String software) { final File directory = getUserConfigurationDirectoryFor(software); if (directory != null) { return directory.getAbsolutePath(); } return null; } /** Replies the system configuration directory for the specified software. * * <p>On Unix operating systems, the system directory for a * software is by default {@code /etc/software} where {@code software} * is the given parameter (case-sensitive). On Windows® operating systems, the user * directory for a software is by default * {@code C:<span>\</span>Program Files<span>\</span>software} * where {@code software} is the given parameter (case-insensitive). * * @param software is the name of the concerned software. * @return the configuration directory of the software for the current user. */ @Pure public static File getSystemConfigurationDirectoryFor(String software) { if (software == null || "".equals(software)) { //$NON-NLS-1$ throw new IllegalArgumentException(); } final OperatingSystem os = OperatingSystem.getCurrentOS(); if (os == OperatingSystem.ANDROID) { return join(File.listRoots()[0], Android.CONFIGURATION_DIRECTORY, Android.makeAndroidApplicationName(software)); } else if (os.isUnixCompliant()) { final File[] roots = File.listRoots(); return join(roots[0], "etc", software); //$NON-NLS-1$ } else if (os == OperatingSystem.WIN) { File pfDirectory; for (final File root : File.listRoots()) { pfDirectory = new File(root, "Program Files"); //$NON-NLS-1$ if (pfDirectory.isDirectory()) { return new File(root, software); } } } return null; } /** Replies the user configuration directory for the specified software. * * <p>On Unix operating systems, the system directory for a * software is by default {@code /etc/software} where {@code software} * is the given parameter (case-sensitive). On Windows® operating systems, the user * directory for a software is by default * {@code C:<span>\</span>Program Files<span>\</span>software} * where {@code software} is the given parameter (case-insensitive). * * @param software is the name of the concerned software. * @return the configuration directory of the software for the current user. */ @Pure public static String getSystemConfigurationDirectoryNameFor(String software) { final File directory = getSystemConfigurationDirectoryFor(software); if (directory != null) { return directory.getAbsolutePath(); } return null; } /** Replies the system shared library directory for the specified software. * * <p>On Unix operating systems, the system directory for a * software is by default {@code /usr/lib/software} where {@code software} * is the given parameter (case-sensitive). On Windows® operating systems, the user * directory for a software is by default * {@code C:<span>\</span>Program Files<span>\</span>software} * where {@code software} is the given parameter (case-insensitive). * * @param software is the name of the concerned software. * @return the configuration directory of the software for the current user. */ @Pure public static File getSystemSharedLibraryDirectoryFor(String software) { if (software == null || "".equals(software)) { //$NON-NLS-1$ throw new IllegalArgumentException(); } final OperatingSystem os = OperatingSystem.getCurrentOS(); if (os == OperatingSystem.ANDROID) { return join(File.listRoots()[0], Android.DATA_DIRECTORY, Android.makeAndroidApplicationName(software)); } else if (os.isUnixCompliant()) { final File[] roots = File.listRoots(); return join(roots[0], "usr", "lib", software); //$NON-NLS-1$ //$NON-NLS-2$ } else if (os == OperatingSystem.WIN) { File pfDirectory; for (final File root : File.listRoots()) { pfDirectory = new File(root, "Program Files"); //$NON-NLS-1$ if (pfDirectory.isDirectory()) { return new File(root, software); } } } return null; } /** Replies the system shared library directory for the specified software. * * <p>On Unix operating systems, the system directory for a * software is by default {@code /usr/lib/software} where {@code software} * is the given parameter (case-sensitive). On Windows® operating systems, the user * directory for a software is by default * {@code C:<span>\</span>Program Files<span>\</span>software} * where {@code software} is the given parameter (case-insensitive). * * @param software is the name of the concerned software. * @return the configuration directory of the software for the current user. */ @Pure public static String getSystemSharedLibraryDirectoryNameFor(String software) { final File f = getSystemSharedLibraryDirectoryFor(software); if (f == null) { return null; } return f.getAbsolutePath(); } /** Convert a string which represents a local file into a File. * * <p>This function supports the naming standards coming for the different * operating systems. * * @param filename the filename. * @return the file, or <code>null</code> if the given filename is <code>null</code> or empty. * @since 13.0 */ @Pure public static File convertStringToFile(String filename) { if (filename == null || "".equals(filename)) { //$NON-NLS-1$ return null; } if (isWindowsNativeFilename(filename)) { return normalizeWindowsNativeFilename(filename); } // Test for malformed filenames. return new File(extractLocalPath(filename).replaceAll( Pattern.quote(UNIX_SEPARATOR_STRING), Matcher.quoteReplacement(File.separator))); } /** Convert an URL which represents a local file or a resource into a File. * * @param url is the URL to convert. * @return the file. * @throws IllegalArgumentException is the URL was malformed. */ @Pure @SuppressWarnings("checkstyle:npathcomplexity") public static File convertURLToFile(URL url) { URL theUrl = url; if (theUrl == null) { return null; } if (URISchemeType.RESOURCE.isURL(theUrl)) { theUrl = Resources.getResource(decodeHTMLEntities(theUrl.getFile())); if (theUrl == null) { theUrl = url; } } URI uri; try { // this is the step that can fail, and so // it should be this step that should be fixed uri = theUrl.toURI(); } catch (URISyntaxException e) { // OK if we are here, then obviously the URL did // not comply with RFC 2396. This can only // happen if we have illegal unescaped characters. // If we have one unescaped character, then // the only automated fix we can apply, is to assume // all characters are unescaped. // If we want to construct a URI from unescaped // characters, then we have to use the component // constructors: try { uri = new URI(theUrl.getProtocol(), theUrl.getUserInfo(), theUrl.getHost(), theUrl.getPort(), decodeHTMLEntities(theUrl.getPath()), decodeHTMLEntities(theUrl.getQuery()), theUrl.getRef()); } catch (URISyntaxException e1) { // The URL is broken beyond automatic repair throw new IllegalArgumentException(Locale.getString("E1", theUrl)); //$NON-NLS-1$ } } if (uri != null && URISchemeType.FILE.isURI(uri)) { final String auth = uri.getAuthority(); String path = uri.getPath(); if (path == null) { path = uri.getRawPath(); } if (path == null) { path = uri.getSchemeSpecificPart(); } if (path == null) { path = uri.getRawSchemeSpecificPart(); } if (path != null) { if (auth == null || "".equals(auth)) { //$NON-NLS-1$ // absolute filename in URI path = decodeHTMLEntities(path); } else { // relative filename in URI, extract it directly path = decodeHTMLEntities(auth + path); } if (Pattern.matches("^" + Pattern.quote(URL_PATH_SEPARATOR) //$NON-NLS-1$ + "[a-zA-Z][:|].*$", path)) { //$NON-NLS-1$ path = path.substring(URL_PATH_SEPARATOR.length()); } return new File(path); } } throw new IllegalArgumentException(Locale.getString("E2", theUrl)); //$NON-NLS-1$ } /** Convert a string to an URL according to several rules. * * <p>The rules are (the first succeeded is replied): * <ul> * <li>if {@code urlDescription} is <code>null</code> or empty, return <code>null</code>;</li> * <li>try to build an {@link URL} with {@code urlDescription} as parameter;</li> * <li>if {@code allowResourceSearch} is <code>true</code> and * {@code urlDescription} starts with {@code "resource:"}, call * {@link Resources#getResource(String)} with the rest of the string as parameter;</li> * <li>if {@code allowResourceSearch} is <code>true</code>, call * {@link Resources#getResource(String)} with the {@code urlDescription} as * parameter;</li> * <li>assuming that the {@code urlDescription} is * a filename, call {@link File#toURI()} to retreive an URI and then * {@link URI#toURL()};</li> * <li>If everything else failed, return <code>null</code>.</li> * </ul> * * @param urlDescription is a string which is describing an URL. * @param allowResourceSearch indicates if the convertion must take into account the Java resources. * @return the URL. * @throws IllegalArgumentException is the string could not be formatted to URL. * @see Resources#getResource(String) */ @Pure public static URL convertStringToURL(String urlDescription, boolean allowResourceSearch) { return convertStringToURL(urlDescription, allowResourceSearch, true, true); } /** Convert a string to an URL according to several rules. * * <p>The rules are (the first succeeded is replied): * <ul> * <li>if {@code urlDescription} is <code>null</code> or empty, return <code>null</code>;</li> * <li>try to build an {@link URL} with {@code urlDescription} as parameter;</li> * <li>if {@code allowResourceSearch} is <code>true</code> and * {@code urlDescription} starts with {@code "resource:"}, call * {@link Resources#getResource(String)} with the rest of the string as parameter;</li> * <li>if {@code allowResourceSearch} is <code>true</code>, call * {@link Resources#getResource(String)} with the {@code urlDescription} as * parameter;</li> * <li>if {@code repliesFileURL} is <code>true</code> and * assuming that the {@code urlDescription} is * a filename, call {@link File#toURI()} to retreive an URI and then * {@link URI#toURL()};</li> * <li>If everything else failed, return <code>null</code>.</li> * </ul> * * @param urlDescription is a string which is describing an URL. * @param allowResourceSearch indicates if the convertion must take into account the Java resources. * @param repliesFileURL indicates if urlDescription is allowed to be a filename. * @return the URL. * @throws IllegalArgumentException is the string could not be formatted to URL. * @see Resources#getResource(String) */ @Pure public static URL convertStringToURL(String urlDescription, boolean allowResourceSearch, boolean repliesFileURL) { return convertStringToURL(urlDescription, allowResourceSearch, repliesFileURL, true); } /** Convert a string to an URL according to several rules. * * <p>The rules are (the first succeeded is replied): * <ul> * <li>if {@code urlDescription} is <code>null</code> or empty, return <code>null</code>;</li> * <li>try to build an {@link URL} with {@code urlDescription} as parameter;</li> * <li>if {@code allowResourceSearch} is <code>true</code> and * {@code urlDescription} starts with {@code "resource:"}, call * {@link Resources#getResource(String)} with the rest of the string as parameter;</li> * <li>if {@code allowResourceSearch} is <code>true</code>, call * {@link Resources#getResource(String)} with the {@code urlDescription} as * parameter;</li> * <li>if {@code repliesFileURL} is <code>true</code> and * assuming that the {@code urlDescription} is * a filename, call {@link File#toURI()} to retreive an URI and then * {@link URI#toURL()};</li> * <li>If everything else failed, return <code>null</code>.</li> * </ul> * * @param urlDescription is a string which is describing an URL. * @param allowResourceSearch indicates if the convertion must take into account the Java resources. * @param repliesFileURL indicates if urlDescription is allowed to be a filename. * @param supportWindowsPaths indicates if Windows paths should be treated in particular way. * @return the URL. * @throws IllegalArgumentException is the string could not be formatted to URL. * @see Resources#getResource(String) */ @Pure @SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity", "checkstyle:nestedifdepth"}) static URL convertStringToURL(String urlDescription, boolean allowResourceSearch, boolean repliesFileURL, boolean supportWindowsPaths) { URL url = null; if (urlDescription != null && urlDescription.length() > 0) { if (supportWindowsPaths && isWindowsNativeFilename(urlDescription)) { final File file = normalizeWindowsNativeFilename(urlDescription); if (file != null) { return convertFileToURL(file); } } if (URISchemeType.RESOURCE.isScheme(urlDescription)) { if (allowResourceSearch) { final String resourceName = urlDescription.substring(9); url = Resources.getResource(resourceName); } } else if (URISchemeType.FILE.isScheme(urlDescription)) { final File file = new File(URISchemeType.FILE.removeScheme(urlDescription)); try { url = new URL(URISchemeType.FILE.name(), "", //$NON-NLS-1$ fromFileStandardToURLStandard(file)); } catch (MalformedURLException e) { // } } else { try { url = new URL(urlDescription); } catch (MalformedURLException exception) { // ignore error } } if (url == null) { if (allowResourceSearch) { url = Resources.getResource(urlDescription); } if (url == null && URISchemeType.RESOURCE.isScheme(urlDescription)) { return null; } if (url == null && repliesFileURL) { final String urlPart = URISchemeType.removeAnyScheme(urlDescription); // Try to parse a malformed JAR url: // jar:{malformed-url}!/{entry} if (URISchemeType.JAR.isScheme(urlDescription)) { final int idx = urlPart.indexOf(JAR_URL_FILE_ROOT); if (idx > 0) { final URL jarURL = convertStringToURL(urlPart.substring(0, idx), allowResourceSearch); if (jarURL != null) { try { url = toJarURL(jarURL, urlPart.substring(idx + 2)); } catch (MalformedURLException exception) { // } } } } // Standard local file if (url == null) { try { final File file = new File(urlPart); url = new URL(URISchemeType.FILE.name(), "", //$NON-NLS-1$ fromFileStandardToURLStandard(file)); } catch (MalformedURLException e) { // ignore error } } } } } return url; } /** * Make the given filename absolute from the given root if it is not already absolute. * * <table border="1" width="100%" summary="Cases"> * <thead> * <tr> * <td>{@code filename}</td><td>{@code current}</td><td>Result</td> * </tr> * </thead> * <tr> * <td><code>null</code></td> * <td><code>null</code></td> * <td><code>null</code></td> * </tr> * <tr> * <td><code>null</code></td> * <td><code>/myroot</code></td> * <td><code>null</code></td> * </tr> * <tr> * <td><code>/path/to/file</code></td> * <td><code>null</code></td> * <td><code>/path/to/file</code></td> * </tr> * <tr> * <td><code>path/to/file</code></td> * <td><code>null</code></td> * <td><code>path/to/file</code></td> * </tr> * <tr> * <td><code>/path/to/file</code></td> * <td><code>/myroot</code></td> * <td><code>/path/to/file</code></td> * </tr> * <tr> * <td><code>path/to/file</code></td> * <td><code>/myroot</code></td> * <td><code>/myroot/path/to/file</code></td> * </tr> * </table> * * @param filename is the name to make absolute. * @param current is the current directory which permits to make absolute. * @return an absolute filename. */ @Pure public static File makeAbsolute(File filename, File current) { if (filename == null) { return null; } if (current != null && !filename.isAbsolute()) { try { return new File(current.getCanonicalFile(), filename.getPath()); } catch (IOException exception) { return new File(current.getAbsoluteFile(), filename.getPath()); } } return filename; } /** * Make the given filename absolute from the given root if it is not already absolute. * * <table border="1" width="100%" summary="Cases"> * <thead> * <tr> * <td>{@code filename}</td><td>{@code current}</td><td>Result</td> * </tr> * </thead> * <tr> * <td><code>null</code></td> * <td><code>null</code></td> * <td><code>null</code></td> * </tr> * <tr> * <td><code>null</code></td> * <td><code>/myroot</code></td> * <td><code>null</code></td> * </tr> * <tr> * <td><code>file:/path/to/file</code></td> * <td><code>null</code></td> * <td><code>file:/path/to/file</code></td> * </tr> * <tr> * <td><code>file:path/to/file</code></td> * <td><code>null</code></td> * <td><code>file:path/to/file</code></td> * </tr> * <tr> * <td><code>file:/path/to/file</code></td> * <td><code>/myroot</code></td> * <td><code>file:/path/to/file</code></td> * </tr> * <tr> * <td><code>file:path/to/file</code></td> * <td><code>/myroot</code></td> * <td><code>file:/myroot/path/to/file</code></td> * </tr> * <tr> * <td><code>http://host.com/path/to/file</code></td> * <td><code>null</code></td> * <td><code>http://host.com/path/to/file</code></td> * </tr> * <tr> * <td><code>http://host.com/path/to/file</code></td> * <td><code>/myroot</code></td> * <td><code>http://host.com/path/to/file</code></td> * </tr> * <tr> * <td><code>ftp://host.com/path/to/file</code></td> * <td><code>null</code></td> * <td><code>ftp://host.com/path/to/file</code></td> * </tr> * <tr> * <td><code>ftp://host.com/path/to/file</code></td> * <td><code>/myroot</code></td> * <td><code>ftp://host.com/path/to/file</code></td> * </tr> * <tr> * <td><code>ssh://host.com/path/to/file</code></td> * <td><code>null</code></td> * <td><code>ssh://host.com/path/to/file</code></td> * </tr> * <tr> * <td><code>ssh://host.com/path/to/file</code></td> * <td><code>/myroot</code></td> * <td><code>ssh://host.com/path/to/file</code></td> * </tr> * </table> * * @param filename is the name to make absolute. * @param current is the current directory which permits to make absolute. * @return an absolute filename. */ @Pure public static URL makeAbsolute(URL filename, File current) { try { return makeAbsolute(filename, current == null ? null : current.toURI().toURL()); } catch (MalformedURLException exception) { // } return filename; } /** * Make the given filename absolute from the given root if it is not already absolute. * * <table border="1" width="100%" summary="Cases"> * <thead> * <tr> * <td>{@code filename}</td><td>{@code current}</td><td>Result</td> * </tr> * </thead> * <tr> * <td><code>null</code></td> * <td><code>null</code></td> * <td><code>null</code></td> * </tr> * <tr> * <td><code>null</code></td> * <td><code>file:/myroot</code></td> * <td><code>null</code></td> * </tr> * <tr> * <td><code>null</code></td> * <td><code>http://host.com/myroot</code></td> * <td><code>null</code></td> * </tr> * <tr> * <td><code>file:path/to/file</code></td> * <td><code>null</code></td> * <td><code>file:path/to/file</code></td> * </tr> * <tr> * <td><code>file:path/to/file</code></td> * <td><code>file:/myroot</code></td> * <td><code>file:/myroot/path/to/file</code></td> * </tr> * <tr> * <td><code>file:path/to/file</code></td> * <td><code>http://host.com/myroot</code></td> * <td><code>http://host.com/myroot/path/to/file</code></td> * </tr> * <tr> * <td><code>file:/path/to/file</code></td> * <td><code>null</code></td> * <td><code>file:/path/to/file</code></td> * </tr> * <tr> * <td><code>file:/path/to/file</code></td> * <td><code>file:/myroot</code></td> * <td><code>file:/path/to/file</code></td> * </tr> * <tr> * <td><code>file:/path/to/file</code></td> * <td><code>http://host.com/myroot</code></td> * <td><code>file:/path/to/file</code></td> * </tr> * <tr> * <td><code>http://host2.com/path/to/file</code></td> * <td><code>null</code></td> * <td><code>http://host2.com/path/to/file</code></td> * </tr> * <tr> * <td><code>http://host2.com/path/to/file</code></td> * <td><code>file:/myroot</code></td> * <td><code>http://host2.com/path/to/file</code></td> * </tr> * <tr> * <td><code>http://host2.com/path/to/file</code></td> * <td><code>http://host.com/myroot</code></td> * <td><code>http://host2.com/path/to/file</code></td> * </tr> * <tr> * <td><code>ftp://host2.com/path/to/file</code></td> * <td><code>null</code></td> * <td><code>ftp://host2.com/path/to/file</code></td> * </tr> * <tr> * <td><code>ftp://host2.com/path/to/file</code></td> * <td><code>file:/myroot</code></td> * <td><code>ftp://host2.com/path/to/file</code></td> * </tr> * <tr> * <td><code>ftp://host2.com/path/to/file</code></td> * <td><code>http://host.com/myroot</code></td> * <td><code>ftp://host2.com/path/to/file</code></td> * </tr> * </table> * * @param filename is the name to make absolute. * @param current is the current directory which permits to make absolute. * @return an absolute filename. */ @Pure @SuppressWarnings("checkstyle:cyclomaticcomplexity") public static URL makeAbsolute(URL filename, URL current) { if (filename == null) { return null; } final URISchemeType scheme = URISchemeType.getSchemeType(filename); switch (scheme) { case JAR: try { URL jarUrl = getJarURL(filename); jarUrl = makeAbsolute(jarUrl, current); final File jarFile = getJarFile(filename); return toJarURL(jarUrl, jarFile); } catch (MalformedURLException exception) { // Ignore error } break; case FILE: final File file = new File(filename.getFile()); if (!file.isAbsolute() && current != null) { return join(current, file); } break; case UNSUPPORTED: case TELNET: case FTP: case HTTP: case HTTPS: case MAILTO: case NEWS: case RESOURCE: case SSH: default: // do not change the URL } return filename; } /** * Make the given filename absolute from the given root if it is not already absolute. * * <table border="1" width="100%" summary="Cases"> * <thead> * <tr> * <td>{@code filename}</td><td>{@code current}</td><td>Result</td> * </tr> * </thead> * <tr> * <td><code>null</code></td> * <td><code>null</code></td> * <td><code>null</code></td> * </tr> * <tr> * <td><code>null</code></td> * <td><code>file:/myroot</code></td> * <td><code>null</code></td> * </tr> * <tr> * <td><code>null</code></td> * <td><code>http://host.com/myroot</code></td> * <td><code>null</code></td> * </tr> * <tr> * <td><code>path/to/file</code></td> * <td><code>null</code></td> * <td><code>file:path/to/file</code></td> * </tr> * <tr> * <td><code>path/to/file</code></td> * <td><code>file:/myroot</code></td> * <td><code>file:/myroot/path/to/file</code></td> * </tr> * <tr> * <td><code>path/to/file</code></td> * <td><code>http://host.com/myroot</code></td> * <td><code>http://host.com/myroot/path/to/file</code></td> * </tr> * <tr> * <td><code>/path/to/file</code></td> * <td><code>null</code></td> * <td><code>file:/path/to/file</code></td> * </tr> * <tr> * <td><code>/path/to/file</code></td> * <td><code>file:/myroot</code></td> * <td><code>file:/path/to/file</code></td> * </tr> * <tr> * <td><code>/path/to/file</code></td> * <td><code>http://host.com/myroot</code></td> * <td><code>file:/path/to/file</code></td> * </tr> * </table> * * @param filename is the name to make absolute. * @param current is the current directory which permits to make absolute. * @return an absolute filename. * @since 5.0 */ @Pure public static URL makeAbsolute(File filename, URL current) { if (filename != null) { if (!filename.isAbsolute() && current != null) { return join(current, filename); } try { return new URL(URISchemeType.FILE.toString() + fromFileStandardToURLStandard(filename.getAbsolutePath())); } catch (MalformedURLException exception) { // ignore error } } return null; } /** Replies the parent URL for the given URL. * * @param url the URL. * @return the parent URL * @throws MalformedURLException if the parent URL cannot be built. */ @Pure @SuppressWarnings("checkstyle:cyclomaticcomplexity") public static URL getParentURL(URL url) throws MalformedURLException { if (url == null) { return url; } String path = url.getPath(); final String prefix; final String parentStr; switch (URISchemeType.getSchemeType(url)) { case JAR: final int index = path.indexOf(JAR_URL_FILE_ROOT); assert index > 0; prefix = path.substring(0, index + 1); path = path.substring(index + 1); parentStr = URL_PATH_SEPARATOR; break; case FILE: prefix = null; parentStr = ".." + URL_PATH_SEPARATOR; //$NON-NLS-1$ break; case FTP: case HTTP: case HTTPS: case MAILTO: case NEWS: case RESOURCE: case SSH: case TELNET: case UNSUPPORTED: default: prefix = null; parentStr = URL_PATH_SEPARATOR; } if (path == null || "".equals(path)) { //$NON-NLS-1$ path = parentStr; } int index = path.lastIndexOf(URL_PATH_SEPARATOR_CHAR); if (index == -1) { path = parentStr; } else if (index == path.length() - 1) { index = path.lastIndexOf(URL_PATH_SEPARATOR_CHAR, index - 1); if (index == -1) { path = parentStr; } else { path = path.substring(0, index + 1); } } else { path = path.substring(0, index + 1); } if (prefix != null) { path = prefix + path; } return new URL(url.getProtocol(), url.getHost(), url.getPort(), path); } /** Test if the given filename is a local filename and extract * the path component. * * @param filename the filename. * @return the path. */ @Pure @SuppressWarnings("checkstyle:magicnumber") private static String extractLocalPath(String filename) { if (filename == null) { return null; } final int max = Math.min(FILE_PREFIX.length, filename.length()); final int inner = max - 2; if (inner <= 0) { return filename; } boolean foundInner = false; boolean foundFull = false; for (int i = 0; i < max; ++i) { final char c = Character.toLowerCase(filename.charAt(i)); if (FILE_PREFIX[i] != c) { foundFull = false; foundInner = i >= inner; break; } foundFull = true; } String fn; if (foundFull) { fn = filename.substring(FILE_PREFIX.length); } else if (foundInner) { fn = filename.substring(inner); } else { fn = filename; } if (Pattern.matches("^(" + Pattern.quote(URL_PATH_SEPARATOR) + "|" //$NON-NLS-1$ //$NON-NLS-2$ + Pattern.quote(WINDOWS_SEPARATOR_STRING) + ")[a-zA-Z][:|].*$", fn)) { //$NON-NLS-1$ fn = fn.substring(1); } return fn; } /** Replies if the given string contains a Windows® native long filename. * * <p>Long filenames (LFN), spelled "long file names" by Microsoft Corporation, * are Microsoft's way of implementing filenames longer than the 8.3, * or short-filename, naming scheme used in Microsoft DOS in their modern * FAT and NTFS filesystems. Because these filenames can be longer than the * 8.3 filename, they can be more descriptive. Another advantage of this * scheme is that it allows for use of *nix files ending in (e.g. .jpeg, * .tiff, .html, and .xhtml) rather than specialized shortened names * (e.g. .jpg, .tif, .htm, .xht). * * <p>The long filename system allows a maximum length of 255 UTF-16 characters, * including spaces and non-alphanumeric characters; excluding the following * characters, which have special meaning within the command interpreter or * the operating system kernel: <code>\</code> <code>/</code> <code>:</code> * <code>*</code> <code>?</code> <code>"</code> <code><</code> * <code>></code> <code>|</code> * * @param filename the filename to test. * @return <code>true</code> if the given filename is a long filename, * otherwise <code>false</code> * @see #normalizeWindowsNativeFilename(String) */ @Pure public static boolean isWindowsNativeFilename(String filename) { final String fn = extractLocalPath(filename); if (fn == null || fn.length() == 0) { return false; } final Pattern pattern = Pattern.compile(WINDOW_NATIVE_FILENAME_PATTERN); final Matcher matcher = pattern.matcher(fn); return matcher.matches(); } /** Normalize the given string contains a Windows® native long filename * and replies a Java-standard version. * * <p>Long filenames (LFN), spelled "long file names" by Microsoft Corporation, * are Microsoft's way of implementing filenames longer than the 8.3, * or short-filename, naming scheme used in Microsoft DOS in their modern * FAT and NTFS filesystems. Because these filenames can be longer than the * 8.3 filename, they can be more descriptive. Another advantage of this * scheme is that it allows for use of *nix files ending in (e.g. .jpeg, * .tiff, .html, and .xhtml) rather than specialized shortened names * (e.g. .jpg, .tif, .htm, .xht). * * <p>The long filename system allows a maximum length of 255 UTF-16 characters, * including spaces and non-alphanumeric characters; excluding the following * characters, which have special meaning within the command interpreter or * the operating system kernel: <code>\</code> <code>/</code> <code>:</code> * <code>*</code> <code>?</code> <code>"</code> <code><</code> * <code>></code> <code>|</code> * * @param filename the filename to test. * @return the normalized path or <code>null</code> if not a windows native path. * @see #isWindowsNativeFilename(String) */ @Pure public static File normalizeWindowsNativeFilename(String filename) { final String fn = extractLocalPath(filename); if (fn != null && fn.length() > 0) { final Pattern pattern = Pattern.compile(WINDOW_NATIVE_FILENAME_PATTERN); final Matcher matcher = pattern.matcher(fn); if (matcher.find()) { return new File(fn.replace(WINDOWS_SEPARATOR_CHAR, File.separatorChar)); } } return null; } /** Replies an URL for the given file and translate it into a * resource URL if the given file is inside the classpath. * * @param file is the filename to translate. * @return the URL which is corresponding to file, or <code>null</code> if * the url cannot be computed. */ @Pure public static URL convertFileToURL(File file) { if (file == null) { return null; } try { File thefile = file; if (isWindowsNativeFilename(file.toString())) { thefile = normalizeWindowsNativeFilename(file.toString()); if (thefile == null) { thefile = file; } } final URL url = thefile.toURI().toURL(); return toShortestURL(url); } catch (MalformedURLException e) { return null; } } /** Replies an URL for the given url and translate it into a * resource URL if the given file is inside the classpath. * * @param url is the URL to make shortest. * @return the URL which is corresponding to file, or <code>null</code> if * the url cannot be computed. * @since 4.0 */ @Pure public static URL toShortestURL(URL url) { if (url == null) { return null; } final String endPattern = URL_PATH_SEPARATOR + "$"; //$NON-NLS-1$ final String shorterUrl = url.toExternalForm().replaceAll(endPattern, ""); //$NON-NLS-1$ String sp; final Iterator<URL> classpath = ClasspathUtil.getClasspath(); URL path; while (classpath.hasNext()) { path = classpath.next(); sp = path.toExternalForm().replaceAll(endPattern, ""); //$NON-NLS-1$ if (shorterUrl.startsWith(sp)) { final StringBuilder buffer = new StringBuilder("resource:"); //$NON-NLS-1$ buffer.append(shorterUrl.substring(sp.length()).replaceAll( "^" + URL_PATH_SEPARATOR, "")); //$NON-NLS-1$ //$NON-NLS-2$ try { return new URL(buffer.toString()); } catch (MalformedURLException e) { // } } } try { return new URL(shorterUrl); } catch (MalformedURLException e) { return url; } } /** * Make the given filename relative to the given root path. * * @param filenameToMakeRelative is the name to make relative. * @param rootPath is the root path from which the relative path will be set. * @return a relative filename. * @throws IOException when is is impossible to retreive canonical paths. */ @Pure public static File makeRelative(File filenameToMakeRelative, File rootPath) throws IOException { return makeRelative(filenameToMakeRelative, rootPath, true); } /** * Make the given filename relative to the given root path. * * @param filenameToMakeRelative is the name to make relative. * @param rootPath is the root path from which the relative path will be set. * @param appendCurrentDirectorySymbol indicates if "./" should be append at the * begining of the relative filename. * @return a relative filename. * @throws IOException when is is impossible to retreive canonical paths. */ private static File makeRelative(File filenameToMakeRelative, File rootPath, boolean appendCurrentDirectorySymbol) throws IOException { if (filenameToMakeRelative == null || rootPath == null) { throw new IllegalArgumentException(); } if (!filenameToMakeRelative.isAbsolute()) { return filenameToMakeRelative; } if (!rootPath.isAbsolute()) { return filenameToMakeRelative; } final File root = rootPath.getCanonicalFile(); final File dir = filenameToMakeRelative.getParentFile().getCanonicalFile(); final String[] parts1 = split(dir); final String[] parts2 = split(root); final String relPath = makeRelative(parts1, parts2, filenameToMakeRelative.getName()); if (appendCurrentDirectorySymbol) { return new File(CURRENT_DIRECTORY, relPath); } return new File(relPath); } /** * Make the given filename relative to the given root path. * * @param filenameToMakeRelative is the name to make relative. * @param rootPath is the root path from which the relative path will be set. * @return a relative filename. * @throws IOException when is is impossible to retreive canonical paths. * @since 6.0 */ @Pure public static File makeRelative(File filenameToMakeRelative, URL rootPath) throws IOException { if (filenameToMakeRelative == null || rootPath == null) { throw new IllegalArgumentException(); } if (!filenameToMakeRelative.isAbsolute()) { return filenameToMakeRelative; } final File dir = filenameToMakeRelative.getParentFile().getCanonicalFile(); final String[] parts1 = split(dir); final String[] parts2 = split(rootPath); final String relPath = makeRelative(parts1, parts2, filenameToMakeRelative.getName()); return new File(CURRENT_DIRECTORY, relPath); } /** * Make the given filename relative to the given root path. * * @param filenameToMakeRelative is the name to make relative. * @param rootPath is the root path from which the relative path will be set. * @return a relative filename. * @throws IOException when is is impossible to retreive canonical paths. * @since 6.0 */ @Pure public static File makeRelative(URL filenameToMakeRelative, URL rootPath) throws IOException { if (filenameToMakeRelative == null || rootPath == null) { throw new IllegalArgumentException(); } final String basename = largeBasename(filenameToMakeRelative); final URL dir = dirname(filenameToMakeRelative); final String[] parts1 = split(dir); final String[] parts2 = split(rootPath); final String relPath = makeRelative(parts1, parts2, basename); return new File(CURRENT_DIRECTORY, relPath); } @SuppressWarnings("checkstyle:npathcomplexity") private static String makeRelative(String[] parts1, String[] parts2, String basename) { int firstDiff = -1; for (int i = 0; firstDiff < 0 && i < parts1.length && i < parts2.length; ++i) { if (!parts1[i].equals(parts2[i])) { firstDiff = i; } } final StringBuilder result = new StringBuilder(); if (firstDiff < 0) { firstDiff = Math.min(parts1.length, parts2.length); } for (int i = firstDiff; i < parts2.length; ++i) { if (result.length() > 0) { result.append(File.separator); } result.append(PARENT_DIRECTORY); } for (int i = firstDiff; i < parts1.length; ++i) { if (result.length() > 0) { result.append(File.separator); } result.append(parts1[i]); } if (result.length() > 0) { result.append(File.separator); } result.append(basename); return result.toString(); } /** Make the given URL canonical. * * <p>A canonical pathname is both absolute and unique. This method maps * the pathname to its unique form. This typically involves removing redundant names * such as <tt>"."</tt> and <tt>".."</tt> from the pathname. * * @param url is the URL to make canonical * @return the canonical form of the given URL. * @since 6.0 */ @Pure public static URL makeCanonicalURL(URL url) { if (url != null) { final String[] pathComponents = url.getPath().split(Pattern.quote(URL_PATH_SEPARATOR)); final List<String> canonicalPath = new LinkedList<>(); for (final String component : pathComponents) { if (!CURRENT_DIRECTORY.equals(component)) { if (PARENT_DIRECTORY.equals(component)) { if (!canonicalPath.isEmpty()) { canonicalPath.remove(canonicalPath.size() - 1); } else { canonicalPath.add(component); } } else { canonicalPath.add(component); } } } final StringBuilder newPathBuffer = new StringBuilder(); boolean isFirst = true; for (final String component : canonicalPath) { if (!isFirst) { newPathBuffer.append(URL_PATH_SEPARATOR_CHAR); } else { isFirst = false; } newPathBuffer.append(component); } try { return new URI( url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), newPathBuffer.toString(), url.getQuery(), url.getRef()).toURL(); } catch (MalformedURLException | URISyntaxException exception) { // } try { return new URL( url.getProtocol(), url.getHost(), newPathBuffer.toString()); } catch (Throwable exception) { // } } return url; } /** * Create a zip file from the given input file. * * @param input the name of the file to compress. * @param output the name of the ZIP file to create. * @throws IOException when ziiping is failing. * @since 6.2 */ public static void zipFile(File input, File output) throws IOException { try (FileOutputStream fos = new FileOutputStream(output)) { zipFile(input, fos); } } /** * Create a zip file from the given input file. * If the input file is a directory, the content of the directory is zipped. * If the input file is a standard file, it is zipped. * * @param input the name of the file to compress. * @param output the name of the ZIP file to create. * @throws IOException when ziiping is failing. * @since 6.2 */ @SuppressWarnings("checkstyle:npathcomplexity") public static void zipFile(File input, OutputStream output) throws IOException { try (ZipOutputStream zos = new ZipOutputStream(output)) { if (input == null) { return; } final LinkedList<File> candidates = new LinkedList<>(); candidates.add(input); final byte[] buffer = new byte[BUFFER_SIZE]; int len; File file; File relativeFile; String zipFilename; final File rootDirectory = (input.isDirectory()) ? input : input.getParentFile(); while (!candidates.isEmpty()) { file = candidates.removeFirst(); assert file != null; if (file.getAbsoluteFile().equals(rootDirectory.getAbsoluteFile())) { relativeFile = null; } else { relativeFile = makeRelative(file, rootDirectory, false); } if (file.isDirectory()) { if (relativeFile != null) { zipFilename = fromFileStandardToURLStandard(relativeFile) + URL_PATH_SEPARATOR; final ZipEntry zipEntry = new ZipEntry(zipFilename); zos.putNextEntry(zipEntry); zos.closeEntry(); } candidates.addAll(Arrays.asList(file.listFiles())); } else if (relativeFile != null) { try (FileInputStream fis = new FileInputStream(file)) { zipFilename = fromFileStandardToURLStandard(relativeFile); final ZipEntry zipEntry = new ZipEntry(zipFilename); zos.putNextEntry(zipEntry); while ((len = fis.read(buffer)) > 0) { zos.write(buffer, 0, len); } zos.closeEntry(); } } } } } /** * Unzip the given stream and write out the file in the output. * If the input file is a directory, the content of the directory is zipped. * If the input file is a standard file, it is zipped. * * @param input the ZIP file to uncompress. * @param output the uncompressed file to create. * @throws IOException when uncompressing is failing. * @since 6.2 */ public static void unzipFile(InputStream input, File output) throws IOException { if (output == null) { return; } output.mkdirs(); if (!output.isDirectory()) { throw new IOException(Locale.getString("E3", output)); //$NON-NLS-1$ } try (ZipInputStream zis = new ZipInputStream(input)) { final byte[] buffer = new byte[BUFFER_SIZE]; int len; ZipEntry zipEntry = zis.getNextEntry(); while (zipEntry != null) { final String name = zipEntry.getName(); final File outFile = new File(output, name).getCanonicalFile(); if (zipEntry.isDirectory()) { outFile.mkdirs(); } else { outFile.getParentFile().mkdirs(); try (FileOutputStream fos = new FileOutputStream(outFile)) { while ((len = zis.read(buffer)) > 0) { fos.write(buffer, 0, len); } } } zipEntry = zis.getNextEntry(); } } } /** * Unzip a file into the output directory. * * @param input the ZIP file to uncompress. * @param output the uncompressed file to create. * @throws IOException when uncompressing is failing. * @since 6.2 */ public static void unzipFile(File input, File output) throws IOException { try (FileInputStream fis = new FileInputStream(input)) { unzipFile(fis, output); } } /** Create an empty directory in the default temporary-file directory, using * the given prefix and suffix to generate its name. Invoking this method * is equivalent to invoking <code>{@link #createTempDirectory(java.lang.String, * java.lang.String, java.io.File) * createTempDirectory(prefix, suffix, null)}</code>. * * @param prefix is the prefix string to be used in generating the file's * name; must be at least three characters long * * @param suffix is the suffix string to be used in generating the file's * name; may be <code>null</code>, in which case the * suffix <code>".tmp"</code> will be used * @return An abstract pathname denoting a newly-created empty file * @throws IllegalArgumentException * If the <code>prefix</code> argument contains fewer than three * characters * @throws IOException If a file could not be created * @throws SecurityException * If a security manager exists and its <code>{@link * java.lang.SecurityManager#checkWrite(java.lang.String)}</code> * method does not allow a file to be created * @since 6.2 */ public static File createTempDirectory(String prefix, String suffix) throws IOException { return createTempDirectory(prefix, suffix, null); } /** Creates a new empty directory in the specified directory, using the * given prefix and suffix strings to generate its name. If this method * returns successfully then it is guaranteed that: * <ol> * <li> The directory denoted by the returned abstract pathname did not exist * before this method was invoked, and * <li> Neither this method nor any of its variants will return the same * abstract pathname again in the current invocation of the virtual * machine. * </ol> * * <p>This method provides only part of a temporary-file facility. To arrange * for a file created by this method to be deleted automatically, use the * <code>{@link #deleteOnExit}</code> method. * * <p>The <code>prefix</code> argument must be at least three characters * long. It is recommended that the prefix be a short, meaningful string * such as <code>"hjb"</code> or <code>"mail"</code>. The * <code>suffix</code> argument may be <code>null</code>, in which case the * suffix <code>".tmp"</code> will be used. * * <p>To create the new directory, the prefix and the suffix may first be * adjusted to fit the limitations of the underlying platform. If the * prefix is too long then it will be truncated, but its first three * characters will always be preserved. If the suffix is too long then it * too will be truncated, but if it begins with a period character * (<code>'.'</code>) then the period and the first three characters * following it will always be preserved. Once these adjustments have been * made the name of the new file will be generated by concatenating the * prefix, five or more internally-generated characters, and the suffix. * * <p>If the <code>directory</code> argument is <code>null</code> then the * system-dependent default temporary-file directory will be used. The * default temporary-file directory is specified by the system property * <code>java.io.tmpdir</code>. On UNIX systems the default value of this * property is typically <code>"/tmp"</code> or <code>"/var/tmp"</code>; on * Microsoft Windows systems it is typically <code>"C:\\WINNT\\TEMP"</code>. A different * value may be given to this system property when the Java virtual machine * is invoked, but programmatic changes to this property are not guaranteed * to have any effect upon the temporary directory used by this method. * * @param prefix is the prefix string to be used in generating the file's * name; must be at least three characters long * * @param suffix is the suffix string to be used in generating the file's * name; may be <code>null</code>, in which case the * suffix <code>".tmp"</code> will be used * @param directory is the directory in which the file is to be created, or * <code>null</code> if the default temporary-file * directory is to be used * @return An abstract pathname denoting a newly-created empty file * @throws IllegalArgumentException * If the <code>prefix</code> argument contains fewer than three * characters * @throws IOException If a file could not be created * @throws SecurityException * If a security manager exists and its <code>{@link * java.lang.SecurityManager#checkWrite(java.lang.String)}</code> * method does not allow a file to be created * @since 6.2 */ public static File createTempDirectory(String prefix, String suffix, File directory) throws IOException { if (prefix == null) { throw new NullPointerException(); } if (prefix.length() < 3) { throw new IllegalArgumentException(Locale.getString("E4", 3, prefix)); //$NON-NLS-1$ } final String string = (suffix == null) ? ".tmp" : suffix; //$NON-NLS-1$ final File targetDirectory; if (directory == null) { targetDirectory = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$ } else { targetDirectory = directory; } File filename; do { long index = RANDOM.nextLong(); if (index == Long.MIN_VALUE) { // corner case index = 0; } else { index = Math.abs(index); } final StringBuilder buffer = new StringBuilder(); buffer.append(prefix); buffer.append(Long.toString(index)); buffer.append(string); filename = new File(targetDirectory, buffer.toString()); } while (!filename.mkdirs()); return filename; } /** Hook to recursively delete files on JVM exit. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 6.0 */ private static class DeleteOnExitHook extends Thread { private List<File> filesToDelete; /** Construct the hook. */ DeleteOnExitHook() { setName("DeleteOnExitHook"); //$NON-NLS-1$ } @Override public void run() { synchronized (this) { if (this.filesToDelete != null) { for (final File f : this.filesToDelete) { try { delete(f); } catch (IOException e) { // Ignore error } } this.filesToDelete.clear(); this.filesToDelete = null; } } } /** Add a file to delete. * * @param file the file to delete. */ public void add(File file) { assert file != null; synchronized (this) { if (this.filesToDelete == null) { this.filesToDelete = new LinkedList<>(); Runtime.getRuntime().addShutdownHook(this); } this.filesToDelete.add(file); } } /** Remove a file to delete. * * @param file the file. */ public void remove(File file) { synchronized (this) { if (this.filesToDelete != null) { this.filesToDelete.remove(file); if (this.filesToDelete.isEmpty()) { this.filesToDelete = null; Runtime.getRuntime().removeShutdownHook(this); } } } } } }