--- /dev/null
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.FileSystems;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class FileUtils
+{
+ /*
+ * Given string glob pattern (see
+ * https://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String)
+ * ) return a List of Files that match the pattern.
+ * Note this is a java style glob, not necessarily a bash-style glob, though there are sufficient similarities.
+ */
+ public static List<File> getFilesFromGlob(String pattern)
+ {
+ return getFilesFromGlob(pattern, true);
+ }
+
+ public static List<File> getFilesFromGlob(String pattern,
+ boolean allowSingleFilenameThatDoesNotExist)
+ {
+ pattern = substituteHomeDir(pattern);
+ String relativePattern = pattern.startsWith(File.separator) ? null
+ : pattern;
+ List<File> files = new ArrayList<>();
+ /*
+ * For efficiency of the Files.walkFileTree(), let's find the longest path that doesn't need globbing.
+ * We look for the first glob character * { ? and then look for the last File.separator before that.
+ * Then we can reset the path to look at and shorten the globbing pattern.
+ * Relative paths can be used in pattern, which work from the pwd (though these are converted into
+ * full paths in the match).
+ */
+ int firstGlobChar = -1;
+ boolean foundGlobChar = false;
+ for (char c : new char[] { '*', '{', '?' })
+ {
+ if (pattern.indexOf(c) > -1
+ && (pattern.indexOf(c) < firstGlobChar || !foundGlobChar))
+ {
+ firstGlobChar = pattern.indexOf(c);
+ foundGlobChar = true;
+ }
+ }
+ int lastFS = pattern.lastIndexOf(File.separatorChar, firstGlobChar);
+ if (foundGlobChar)
+ {
+ String pS = pattern.substring(0, lastFS + 1);
+ String rest = pattern.substring(lastFS + 1);
+ if ("".equals(pS))
+ {
+ pS = ".";
+ }
+ Path parentDir = Paths.get(pS);
+ if (parentDir.toFile().exists())
+ {
+ try
+ {
+ String glob = "glob:" + parentDir.toString() + File.separator
+ + rest;
+ PathMatcher pm = FileSystems.getDefault().getPathMatcher(glob);
+ int maxDepth = rest.contains("**") ? 1028
+ : (int) (rest.chars()
+ .filter(ch -> ch == File.separatorChar).count())
+ + 1;
+
+ Files.walkFileTree(parentDir,
+ EnumSet.of(FileVisitOption.FOLLOW_LINKS), maxDepth,
+ new SimpleFileVisitor<Path>()
+ {
+ @Override
+ public FileVisitResult visitFile(Path path,
+ BasicFileAttributes attrs) throws IOException
+ {
+ if (pm.matches(path))
+ {
+ files.add(path.toFile());
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFileFailed(Path file,
+ IOException exc) throws IOException
+ {
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ else
+ {
+ // no wildcards
+ File f = new File(pattern);
+ if (allowSingleFilenameThatDoesNotExist || f.exists())
+ {
+ files.add(f);
+ }
+ }
+ Collections.sort(files);
+
+ return files;
+ }
+
+ public static List<String> getFilenamesFromGlob(String pattern)
+ {
+ // convert list of Files to list of File.getPath() Strings
+ return getFilesFromGlob(pattern).stream().map(f -> f.getPath())
+ .collect(Collectors.toList());
+ }
+
+ public static String substituteHomeDir(String path)
+ {
+ return path.startsWith("~" + File.separator)
+ ? System.getProperty("user.home") + path.substring(1)
+ : path;
+ }
+
+ /*
+ * This method returns the basename of File file
+ */
+ public static String getBasename(File file)
+ {
+ return getBasenameOrExtension(file, false);
+ }
+
+ /*
+ * This method returns the extension of File file.
+ */
+ public static String getExtension(File file)
+ {
+ return getBasenameOrExtension(file, true);
+ }
+
+ public static String getBasenameOrExtension(File file, boolean extension)
+ {
+ if (file == null)
+ return null;
+
+ String value = null;
+ String filename = file.getName();
+ int lastDot = filename.lastIndexOf('.');
+ if (lastDot > 0) // don't truncate if starts with '.'
+ {
+ value = extension ? filename.substring(lastDot + 1)
+ : filename.substring(0, lastDot);
+ }
+ else
+ {
+ value = extension ? "" : filename;
+ }
+ return value;
+ }
+
+ /*
+ * This method returns the dirname of the first --append or --open value.
+ * Used primarily for substitutions in output filenames.
+ */
+ public static String getDirname(File file)
+ {
+ if (file == null)
+ return null;
+
+ String dirname = null;
+ File p = file.getParentFile();
+ if (p == null)
+ {
+ p = new File(".");
+ }
+ File d = new File(substituteHomeDir(p.getPath()));
+ dirname = d.getPath();
+ return dirname;
+ }
+
+ public static String convertWildcardsToPath(String value, String wildcard,
+ String dirname, String basename)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+ StringBuilder path = new StringBuilder();
+ int lastFileSeparatorIndex = value.lastIndexOf(File.separatorChar);
+ int wildcardBeforeIndex = value.indexOf(wildcard);
+ if (lastFileSeparatorIndex > wildcard.length() - 1
+ && wildcardBeforeIndex < lastFileSeparatorIndex)
+ {
+ path.append(value.substring(0, wildcardBeforeIndex));
+ path.append(dirname);
+ path.append(value.substring(wildcardBeforeIndex + wildcard.length(),
+ lastFileSeparatorIndex + 1));
+ }
+ else
+ {
+ path.append(value.substring(0, lastFileSeparatorIndex + 1));
+ }
+ int wildcardAfterIndex = value.indexOf(wildcard,
+ lastFileSeparatorIndex);
+ if (wildcardAfterIndex > lastFileSeparatorIndex)
+ {
+ path.append(value.substring(lastFileSeparatorIndex + 1,
+ wildcardAfterIndex));
+ path.append(basename);
+ path.append(value.substring(wildcardAfterIndex + wildcard.length()));
+ }
+ else
+ {
+ path.append(value.substring(lastFileSeparatorIndex + 1));
+ }
+ return path.toString();
+ }
+
+ public static File getParentDir(File file)
+ {
+ if (file == null)
+ {
+ return null;
+ }
+ File parentDir = file.getAbsoluteFile().getParentFile();
+ return parentDir;
+ }
+
+ public static boolean checkParentDir(File file, boolean mkdirs)
+ {
+ if (file == null)
+ {
+ return false;
+ }
+ File parentDir = getParentDir(file);
+ if (parentDir.exists())
+ {
+ // already exists, nothing to do so nothing to worry about!
+ return true;
+ }
+
+ if (!mkdirs)
+ {
+ return false;
+ }
+
+ Path path = file.toPath();
+ for (int i = 0; i < path.getNameCount(); i++)
+ {
+ Path p = path.getName(i);
+ if ("..".equals(p.toString()))
+ {
+ LaunchUtils.syserr(true, false,
+ "Cautiously not running mkdirs on " + file.toString()
+ + " because the path to be made contains '..'");
+ return false;
+ }
+ }
+
+ return parentDir.mkdirs();
+ }
+
+ /**
+ * get a guessed file extension from a String only
+ *
+ * @param String
+ * filename
+ * @return String extension
+ */
+ public static String getExtension(String filename)
+ {
+ return getBaseOrExtension(filename, true);
+ }
+
+ /**
+ * getBase returns everything in a path/URI up to (and including) an extension
+ * dot. Note this is not the same as getBasename() since getBasename() only
+ * gives the filename base, not the path too. If no extension dot is found
+ * (i.e. a dot in character position 2 or more of the filename (after the last
+ * slash) then the whole path is considered the base.
+ *
+ * @param filename
+ * @return String base
+ */
+ public static String getBase(String filename)
+ {
+ return getBaseOrExtension(filename, false);
+ }
+
+ public static String getBaseOrExtension(String filename0,
+ boolean extension)
+ {
+ if (filename0 == null)
+ {
+ return null;
+ }
+ String filename = filename0;
+ boolean isUrl = false;
+ if (HttpUtils.startsWithHttpOrHttps(filename))
+ {
+ try
+ {
+ URL url = new URL(filename);
+ filename = url.getPath();
+ isUrl = true;
+ } catch (MalformedURLException e)
+ {
+ // continue to treat as a filename
+ }
+ }
+ int dot = filename.lastIndexOf('.');
+ int slash = filename.lastIndexOf('/');
+ if (!File.separator.equals("/") && !isUrl)
+ {
+ slash = filename.lastIndexOf(File.separator);
+ }
+ // only the dot of the filename (not dots in path) and not if it's a .hidden
+ // file
+ boolean hasExtension = dot > slash + 1;
+ if (extension)
+ {
+ return hasExtension ? filename.substring(dot + 1) : null;
+ }
+ else
+ {
+ dot = filename0.lastIndexOf('.');
+ return hasExtension ? filename0.substring(0, dot + 1) : filename0;
+ }
+ }
+
+ public static Path getCanonicalPath(Path path)
+ {
+ return path.normalize();
+ }
+
+ public static Path getCanonicalPath(File file)
+ {
+ return getCanonicalPath(file.toPath());
+ }
+
+ public static Path getCanonicalPath(String pathString)
+ {
+ return getCanonicalPath(Paths.get(pathString));
+ }
+
+ public static File getCanonicalFile(File file)
+ {
+ return getCanonicalPath(file).toFile();
+ }
+
+ public static File getCanonicalFile(String pathString)
+ {
+ return getCanonicalPath(Paths.get(pathString)).toFile();
+ }
+
+ public static boolean mkdirs(File file)
+ {
+ try
+ {
+ Files.createDirectory(getCanonicalPath(file));
+ return true;
+ } catch (IOException e)
+ {
+ LaunchUtils.syserr(true, false, "Failed to make directory " + file
+ + "\n" + e.getStackTrace());
+ }
+ return false;
+ }
+}
}
/**
- * wrapper to follow a URL connection ALLOWING redirects from http to https
+ * wrapper to return a new HttpURLConnection to a new URL when there is a
+ * redirect from http to https, otherwise return the unused original
+ * HttpURLConnection
*
* @param HttpURLConnection
* conn0
public static HttpURLConnection followConnection(HttpURLConnection conn0)
throws IOException
{
+ return followConnection(conn0, false);
+ }
+
+ public static HttpURLConnection followConnection(HttpURLConnection conn0,
+ boolean followAnyway) throws IOException
+ {
URL url = conn0.getURL();
+ // we are only checking for a redirect from http to https otherwise the java
+ // connection will follow when called (if not unset)
if (url == null)
{
- return null;
+ return conn0;
+ }
+ if (!conn0.getInstanceFollowRedirects())
+ {
+ return conn0;
+ }
+ if (!"http".equals(url.getProtocol()) && !followAnyway)
+ {
+ return conn0;
}
- HttpURLConnection conn = null;
- int response = conn0.getResponseCode();
- boolean followed = false;
- if (response >= 300 && response < 400 && conn0.getFollowRedirects())
+
+ // check the response code
+ HttpURLConnection checkConn = (HttpURLConnection) url.openConnection();
+ httpURLConnectionCopyAttributes(conn0, checkConn);
+
+ boolean redirectToHttps = false;
+ int response = checkConn.getResponseCode();
+ checkConn.disconnect();
+ if (response >= 300 && response < 400)
{
// we are only checking for a redirect from http to https
- if ("http".equals(url.getProtocol()))
+ URL loc = new URL(conn0.getHeaderField("Location"));
+ if (loc != null && "https".equals(loc.getProtocol()))
{
- URL loc = new URL(conn0.getHeaderField("Location"));
- if (loc != null && "https".equals(loc.getProtocol()))
- {
- conn = (HttpURLConnection) loc.openConnection();
- conn.setRequestMethod(conn0.getRequestMethod());
- conn.setDoInput(conn0.getDoInput());
- conn.setUseCaches(conn0.getUseCaches());
- conn.setConnectTimeout(conn0.getConnectTimeout());
- conn.setReadTimeout(conn0.getReadTimeout());
- conn.setInstanceFollowRedirects(
- conn0.getInstanceFollowRedirects());
- followed = true;
- }
+ redirectToHttps = true;
+ url = loc;
}
}
- return followed && conn != null ? conn : conn0;
+ else if (followAnyway)
+ {
+ // checkConn might have followed a https->https redirect
+ url = checkConn.getURL();
+ }
+
+ if (!redirectToHttps && !followAnyway)
+ {
+ return conn0;
+ }
+
+ // We want to return an HttpURLConnection to the new (probably https) URL
+ // that is unconnected in case further manipulation of the request is
+ // required.
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ httpURLConnectionCopyAttributes(conn0, conn);
+ return conn;
+ }
+
+ private static void httpURLConnectionCopyAttributes(
+ HttpURLConnection conn0, HttpURLConnection conn1)
+ throws ProtocolException
+ {
+ conn1.setRequestMethod(conn0.getRequestMethod());
+ conn1.setDoInput(conn0.getDoInput());
+ conn1.setUseCaches(conn0.getUseCaches());
+ conn1.setConnectTimeout(conn0.getConnectTimeout());
+ conn1.setReadTimeout(conn0.getReadTimeout());
+ conn1.setInstanceFollowRedirects(conn0.getInstanceFollowRedirects());
}
/**
*/
public static URLConnection openConnection(URL url) throws IOException
{
+ return openConnection(url, false);
+ }
+
+ public static URLConnection openConnection(URL url, boolean followAnyway)
+ throws IOException
+ {
if (url == null)
{
+ LaunchUtils.syserr(false, true,
+ "HttpUtils.openConnection(url) called with null url");
return null;
}
+ LaunchUtils.syserr(false, true,
+ "HttpUtils.openConnection(url) called with url="
+ + url.toString());
URLConnection conn = null;
String protocol = url.getProtocol();
if ("http".equals(protocol) || "https".equals(protocol))
HttpURLConnection conn0 = (HttpURLConnection) url.openConnection();
if (conn0 != null)
{
- conn = HttpUtils.followConnection(conn0);
+ conn = HttpUtils.followConnection(conn0, followAnyway);
}
else
{
*/
public static InputStream openStream(URL url) throws IOException
{
+ return openStream(url, false);
+ }
+
+ public static InputStream openStream(URL url, boolean followAnyway)
+ throws IOException
+ {
if (url == null)
{
return null;
String protocol = url.getProtocol();
if ("http".equals(protocol) || "https".equals(protocol))
{
- HttpURLConnection conn = HttpUtils
- .followConnection((HttpURLConnection) url.openConnection());
+ HttpURLConnection conn = HttpUtils.followConnection(
+ (HttpURLConnection) url.openConnection(), followAnyway);
if (conn != null)
{
is = conn.getInputStream();