/* * Copyright (C) 2004-2008 Jive Software. All rights reserved. * * 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.jivesoftware.xmpp.workgroup.utils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.net.URL; import java.net.URLConnection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <code>URLFileSystem</code> class handles some of the most common * functionallity when working with URLs. * * @version 1.0, 03/12/14 */ public class URLFileSystem { private static final Logger Log = LoggerFactory.getLogger(URLFileSystem.class); public static void main(String args[]) { } /** * Copies the contents at <CODE>src</CODE> to <CODE>dst</CODE>. */ public static void copy(URL src, File dst) throws IOException { InputStream in = null; OutputStream out = null; try { in = src.openStream(); out = new FileOutputStream(dst); dst.mkdirs(); copy(in, out); } finally { try { if (in != null) in.close(); } catch (IOException e) { } try { if (out != null) out.close(); } catch (IOException e) { } } } /** * Common code for copy routines. By convention, the streams are * closed in the same method in which they were opened. Thus, * this method does not close the streams when the copying is done. */ private static void copy(InputStream in, OutputStream out) throws IOException { final byte[] buffer = new byte[4096]; while (true) { final int bytesRead = in.read(buffer); if (bytesRead < 0) { break; } out.write(buffer, 0, bytesRead); } } /** * If a dot ('.') occurs in the path portion of the {@link URL}, then * all of the text starting at the last dot is returned, including * the dot. If the last dot is also the last character in the path, * then the dot by itself is returned. If there is no dot in the * path, then the empty string is returned. */ public static String getSuffix(URL url) { final String path = url.getPath(); int lastDot = path.lastIndexOf('.'); return (lastDot >= 0) ? path.substring(lastDot) : ""; } //-------------------------------------------------------------------------- // URLFileSystemHelper public API... //-------------------------------------------------------------------------- /** * Returns a canonical form of the {@link URL}, if one is available. * <P> * <p/> * The default implementation just returns the specified {@link URL} * as-is. */ public URL canonicalize(URL url) { return url; } /** * Tests whether the application can read the resource at the * specified {@link URL}. * * @return <CODE>true</CODE> if and only if the specified * {@link URL} points to a resource that exists <EM>and</EM> can be * read by the application; <CODE>false</CODE> otherwise. */ public boolean canRead(URL url) { try { final URLConnection urlConnection = url.openConnection(); return urlConnection.getDoInput(); } catch (Exception e) { return false; } } /** * Tests whether the application can modify the resource at the * specified {@link URL}. * * @return <CODE>true</CODE> if and only if the specified * {@link URL} points to a file that exists <EM>and</EM> the * application is allowed to write to the file; <CODE>false</CODE> * otherwise. */ public boolean canWrite(URL url) { try { final URLConnection urlConnection = url.openConnection(); return urlConnection.getDoOutput(); } catch (Exception e) { return false; } } /** * Tests whether the application can create the resource at the specified * {@link URL}. * * @return <CODE>true</CODE> if the resource at the specified {@link URL} * exists or can be created; <CODE>false</CODE> otherwise. */ public boolean canCreate(URL url) { return true; } /** * Tests whether the specified {@link URL} is valid. If the resource * pointed by the {@link URL} exists the method returns <CODE>true</CODE>. * If the resource does not exist, the method tests that all components * of the path can be created. * * @return <CODE>true</CODE> if the {@link URL} is valid. */ public boolean isValid(URL url) { if (exists(url)) { return true; } return canCreate(url); } /** * Returns <CODE>true</CODE> if the specified {@link URL} points to a * resource that currently exists; returns <CODE>false</CODE> * otherwise.<P> * <p/> * The default implementation simply returns <CODE>false</CODE> * without doing anything. */ public static boolean exists(URL url) { return url2File(url).exists(); } public static boolean mkdirs(URL url) { final File file = url2File(url); if (!file.exists()) { return file.mkdirs(); } return true; } /** * Returns the name of the file contained by the {@link URL}, not * including any protocol, hostname authentication, directory path, * anchor, or query. This simply returns the simple filename. For * example, if you pass in an {@link URL} whose string representation * is: * <p/> * <BLOCKQUOTE><CODE> * protocol://host:1010/dir1/dir2/file.ext#anchor?query * </CODE></BLOCKQUOTE> * <p/> * the returned value is "<CODE>file.ext</CODE>" (without the * quotes).<P> * <p/> * The returned file name should only be used for display purposes * and not for opening streams or otherwise trying to locate the * resource indicated by the {@link URL}. */ public static String getFileName(URL url) { if (url == null) { return ""; } final String path = url.getPath(); if (path.equals("/")) { return "/"; } final int lastSep = path.lastIndexOf('/'); if (lastSep == path.length() - 1) { final int lastSep2 = path.lastIndexOf('/', lastSep - 1); return path.substring(lastSep2 + 1, lastSep); } else { return path.substring(lastSep + 1); } } /** * Returns the number of bytes contained in the resource that the * specified {@link URL} points to. If the length cannot be * determined, <CODE>-1</CODE> is returned.<P> * <p/> * The default implementation attempts to get the content length from * the {@link URLConnection} associated with the {@link URL}. If that * fails for some reason (e.g. the resource does not exist, there was * some other an I/O exception, etc.), <CODE>-1</CODE> is returned. * * @see URLConnection */ public long getLength(URL url) { try { final URLConnection urlConnection = url.openConnection(); return urlConnection.getContentLength(); } catch (Exception e) { return -1; } } /** * Returns the name of the file contained by the {@link URL}, not * including any protocol, hostname authentication, directory path, * anchor, or query. This simply returns the simple filename. For * example, if you pass in an {@link URL} whose string representation * is: * <p/> * <BLOCKQUOTE><CODE> * protocol://host:1010/dir1/dir2/file.ext1.ext2#anchor?query * </CODE></BLOCKQUOTE> * <p/> * the returned value is "<CODE>file</CODE>" (without the quotes).<P> * <p/> * The returned file name should only be used for display purposes * and not for opening streams or otherwise trying to locate the * resource indicated by the {@link URL}.<P> * <p/> * The default implementation first calls {@link #getFileName(URL)} to * get the file name part. Then all characters starting with the * first occurrence of '.' are removed. The remaining string is then * returned. */ public static String getName(URL url) { final String fileName = getFileName(url); final int firstDot = fileName.indexOf('.'); return firstDot > 0 ? fileName.substring(0, firstDot) : fileName; } /** * Returns the path part of the {@link URL}. The returned string * is acceptable to use in one of the {@link URLFactory} methods * that takes a path.<P> * <p/> * The default implementation delegates to {@link URL#getPath()}. */ public String getPath(URL url) { return url.getPath(); } /** * Returns the path part of the {@link URL} without the last file * extension. To clarify, the following examples demonstrate the * different cases that come up: * <p/> * <TABLE BORDER COLS=2 WIDTH="100%"> * <TR> * <TD><CENTER>Path part of input {@link URL}</CENTER></TD> * <TD><CENTER>Output {@link String}</CENTER</TD> * </TR> * <TR> * <TD><CODE>/dir/file.ext</CODE></TD> * <TD><CODE>/dir/file</CODE></TD> * <TR> * <TR> * <TD><CODE>/dir/file.ext1.ext2</CODE></TD> * <TD><CODE>/dir/file.ext1</CODE></TD> * <TR> * <TR> * <TD><CODE>/dir1.ext1/dir2.ext2/file.ext1.ext2</CODE></TD> * <TD><CODE>/dir1.ext1/dir2.ext2/file.ext1</CODE></TD> * <TR> * <TR> * <TD><CODE>/file.ext</CODE></TD> * <TD><CODE>/file</CODE></TD> * <TR> * <TR> * <TD><CODE>/dir.ext/file</CODE></TD> * <TD><CODE>/dir.ext/file</CODE></TD> * <TR> * <TR> * <TD><CODE>/dir/file</CODE></TD> * <TD><CODE>/dir/file</CODE></TD> * <TR> * <TR> * <TD><CODE>/file</CODE></TD> * <TD><CODE>/file</CODE></TD> * <TR> * <TR> * <TD><CODE>/.ext</CODE></TD> * <TD><CODE>/</CODE></TD> * <TR> * </TABLE> * <p/> * The default implementation gets the path from {@link * #getPath(URL)} and then trims off all of the characters beginning * with the last "." in the path, if and only if the last "." comes * after the last "/" in the path. If the last "." comes before * the last "/" or if there is no "." at all, then the entire path * is returned. */ public String getPathNoExt(URL url) { final String path = getPath(url); final int lastSlash = path.lastIndexOf("/"); final int lastDot = path.lastIndexOf("."); if (lastDot <= lastSlash) { // When the lastDot < lastSlash, it means that one of the // directories has an extension, but the filename itself has // no extension. In this case, returning the whole path is // the correct behavior. // // The only time that lastDot and lastSlash can be equal occurs // when both of them are -1. In that case, returning the whole // path is the correct behavior. return path; } // At this point, we know that lastDot must be non-negative, so // we can return the whole path string up to the last dot. return path.substring(0, lastDot); } /** * Returns the platform-dependent String representation of the * {@link URL}; the returned string should be considered acceptable * for users to read. In general, the returned string should omit * as many parts of the {@link URL} as possible. For the "file" * protocol, therefore, the platform pathname should just be the * pathname alone (no protocol) using the appropriate file separator * character for the current platform. For other protocols, it may * be necessary to reformat the {@link URL} string into a more * human-readable form. That decision is left to each * <CODE>URLFileSystemHelper</CODE> implementor. * <p/> * The default implementation returns <CODE>url.toString()</CODE>. * If the {@link URL} is <CODE>null</CODE>, the empty string is * returned. * * @return The path portion of the specified {@link URL} in * platform-dependent notation. This value should only be used for * display purposes and not for opening streams or otherwise trying * to locate the document. */ public String getPlatformPathName(URL url) { return url != null ? url.toString() : ""; } public static URL newFileURL(File file) { String filePath = file.getPath(); if (filePath == null) { return null; } final String path = sanitizePath(filePath); return newURL("file", path); } public static URL newFileURL(String filePath) { if (filePath == null) { return null; } final String path = sanitizePath(filePath); return newURL("file", path); } /** * This "sanitizes" the specified string path by converting all * {@link File#separatorChar} characters to forward slash ('/'). * Also, a leading forward slash is prepended if the path does * not begin with one. */ private static String sanitizePath(String path) { if (File.separatorChar != '/') { path = path.replace(File.separatorChar, '/'); } if (!path.startsWith("/")) { path = "/" + path; } return path; } public static URL newURL(String protocol, String path) { return newURL(protocol, null, null, -1, path, null, null); } //-------------------------------------------------------------------------- // direct access factory methods... //-------------------------------------------------------------------------- /** * Creates a new {@link URL} whose parts have the exact values that * are specified. <EM>In general, you should avoid calling this * method directly.</EM><P> * <p/> * This method is the ultimate place where all of the other * <CODE>URLFactory</CODE> methods end up when creating an * {@link URL}. * <p/> * Non-sanitizing. */ public static URL newURL(String protocol, String userinfo, String host, int port, String path, String query, String ref) { try { final URL seed = new URL(protocol, "", -1, ""); final String authority = port < 0 ? host : host + ":" + port; final Object[] args = new Object[] { protocol, host, new Integer(port), authority, userinfo, path, query, ref, }; // IMPORTANT -- this *MUST* be the only place in URLFactory where // the URL.set(...) method is used. --jdijamco urlSet.invoke(seed, args); return seed; } catch (Exception e) { Log.error(e.getMessage(), e); return null; } } /** * This {@link java.lang.reflect.Method} is used to work-around a bug in Sun's * <CODE>java.net.URL</CODE> implementation. The {@link java.lang.reflect.Method} * allows us to set the parts of an {@link URL} directly. */ private static final Method urlSet; static { final Class str = String.class; try { urlSet = URL.class.getDeclaredMethod("set", new Class[]{str, str, int.class, str, str, str, str, str}); // IMPORTANT: This call to setAccessible effectively overrides // the "protected" visibility constraint on the URL.set(...) // method. This is an intentional breaking of encapsulation to // work-around severe bugs in Sun's java.net.URL implementation // having to do with: // * poor handling of special characters like #, ?, and ; // * poor handling of whitespace // * no go way to disambiguate UNC paths on Win32 // // The use of setAccessible is an implementation detail of the // URLFactory, and if Sun some day fixes their java.net.URL // implementation to address the problems above, we may be able // to change the internal mechanism to use the regular URL // constructors. For the time being, after having weighed the // various other alternatives and even tried some of them (and // encountered other problems), our decision is to force our way // through to the URL.set(...) method, taking care to invoke // it method exactly once per URL object with the exactly the // right arguments. // // --jdijamco March 14, 2001 urlSet.setAccessible(true); } catch (NoSuchMethodException e) { //!jdijamco -- Have some fallback option so that <clinit> doesn't //!jdijamco -- just totally barf and prevent the IDE from starting? throw new IllegalStateException(); } } public static final File url2File(URL url) { final String path = url.getPath(); final File file = new File(path); return file; } public static URL getParent(URL url) { final File file = url2File(url); final File parentFile = file.getParentFile(); if (parentFile != null && !file.equals(parentFile)) { try { return parentFile.toURL(); } catch (Exception ex) { return null; } } return null; } }