/* * Utilities used to manipulate files * * Copyright (c) 2004-2008 The Regents of the University of California. All * rights reserved. Permission is hereby granted, without written agreement and * without license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the above * copyright notice and the following two paragraphs appear in all copies of * this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN * "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.Writer; import java.net.URI; import java.net.URL; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; // Avoid importing any packages from ptolemy.* here so that we // can ship Ptplot. // //////////////////////////////////////////////////////////////////////// // // FileUtilities /** * A collection of utilities for manipulating files These utilities do not * depend on any other ptolemy.* packages. * * @author Christopher Brooks * @version $Id: FileUtilities.java,v 1.54.4.3 2008/03/25 22:32:41 cxh Exp $ * @since Ptolemy II 4.0 * @Pt.ProposedRating Green (cxh) * @Pt.AcceptedRating Green (cxh) */ public class FileUtilities { /** * Instances of this class cannot be created. */ private FileUtilities() { } // ///////////////////////////////////////////////////////////////// // // public methods //// /** * Copy sourceURL to destinationFile without doing any byte conversion. * * @param sourceURL * The source URL * @param destinationFile * The destination File. * @return true if the file was copied, false if the file was not copied * because the sourceURL and the destinationFile refer to the same * file. * @exception IOException * If the source file does not exist. */ public static boolean binaryCopyURLToFile(URL sourceURL, File destinationFile) throws IOException { URL destinationURL = destinationFile.getCanonicalFile().toURI().toURL(); if (sourceURL.sameFile(destinationURL)) { return false; } // If sourceURL is of the form file:./foo, then we need to try again. File sourceFile = new File(sourceURL.getFile()); // If the sourceURL is not a jar URL, then check to see if we // have the same file. // FIXME: should we check for !/ and !\ everywhere? if ((sourceFile.getPath().indexOf("!/") == -1) && (sourceFile.getPath().indexOf("!\\") == -1)) { try { if (sourceFile.getCanonicalFile().toURI().toURL().sameFile(destinationURL)) { return false; } } catch (IOException ex) { // JNLP Jar urls sometimes throw an exception here. // IOException constructor does not take a cause IOException ioException = new IOException("Cannot find canonical file name of '" + sourceFile + "'"); ioException.initCause(ex); throw ioException; } } _binaryCopyStream(sourceURL.openStream(), destinationFile); return true; } /** * Extract a jar file into a directory. This is a trivial implementation of * the <code>jar -xf</code> command. * * @param jarFileName * The name of the jar file to extract * @param directoryName * The name of the directory. If this argument is null, then the * files are extracted in the current directory. * @exception IOException * If the jar file cannot be opened, or if there are problems * extracting the contents of the jar file */ public static void extractJarFile(String jarFileName, String directoryName) throws IOException { JarFile jarFile = new JarFile(jarFileName); Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); File destinationFile = new File(directoryName, jarEntry.getName()); if (jarEntry.isDirectory()) { if (!destinationFile.isDirectory() && !destinationFile.mkdirs()) { throw new IOException("Warning, failed to create " + "directory for \"" + destinationFile + "\"."); } } else { _binaryCopyStream(jarFile.getInputStream(jarEntry), destinationFile); } } } /** * Extract the contents of a jar file. * * @param args * An array of arguments. The first argument names the jar file * to be extracted. The first argument is required. The second * argument names the directory in which to extract the files * from the jar file. The second argument is optional. */ public static void main(String[] args) { if (args.length < 1 || args.length > 2) { System.err.println("Usage: java -classpath $PTII " + "ptolemy.util.FileUtilities jarFile [directory]\n" + "where jarFile is the name of the jar file\n" + "and directory is the optional directory in which to " + "extract."); StringUtilities.exit(2); } String jarFileName = args[0]; String directoryName = null; if (args.length >= 2) { directoryName = args[1]; } try { extractJarFile(jarFileName, directoryName); } catch (Throwable throwable) { System.err.println("Failed to extract \"" + jarFileName + "\""); throwable.printStackTrace(); StringUtilities.exit(3); } } /** * Given a file name or URL, construct a java.io.File object that refers to * the file name or URL. This method first attempts to directly use the file * name to construct the File. If the resulting File is a relative pathname, * then it is resolved relative to the specified base URI, if there is one. * If there is no such base URI, then it simply returns the relative File * object. See the java.io.File documentation for a details about relative * and absolute pathnames. * * <p> * The file need not exist for this method to succeed. Thus, this method can * be used to determine whether a file with a given name exists, prior to * calling openForWriting(), for example. * * <p> * This method is similar to {@link #nameToURL(String, URI, ClassLoader)} * except that in this method, the file or URL must be readable. Usually, * this method is use for write a file and * {@link #nameToURL(String, URI, ClassLoader)} is used for reading. * * @param name * The file name or URL. * @param base * The base for relative URLs. * @return A File, or null if the filename argument is null or an empty * string. * @see #nameToURL(String, URI, ClassLoader) */ public static File nameToFile(String name, URI base) { if ((name == null) || name.trim().equals("")) { return null; } File file = new File(name); if (!file.isAbsolute()) { // Try to resolve the base directory. if (base != null) { URI newURI = base.resolve(name); // file = new File(newURI); String urlString = newURI.getPath(); file = new File(StringUtilities.substitute(urlString, "%20", " ")); } } return file; } /** * Given a file or URL name, return as a URL. If the file name is relative, * then it is interpreted as being relative to the specified base directory. * If the name begins with "xxxxxxCLASSPATHxxxxxx" or "$CLASSPATH" then * search for the file relative to the classpath. * * <p> * Note that "xxxxxxCLASSPATHxxxxxx" is the value of the globally defined * constant $CLASSPATH available in the Ptolemy II expression language. * * <p> * If no file is found, then throw an exception. * * <p> * This method is similar to {@link #nameToFile(String, URI)} except that in * this method, the file or URL must be readable. Usually, this method is * use for reading a file and is used for writing * {@link #nameToFile(String, URI)}. * * @param name * The name of a file or URL. * @param baseDirectory * The base directory for relative file names, or null to specify * none. * @param classLoader * The class loader to use to locate system resources, or null to * use the system class loader that was used to load this class. * @return A URL, or null if the name is null or the empty string. * @exception IOException * If the file cannot be read, or if the file cannot be * represented as a URL (e.g. System.in), or the name * specification cannot be parsed. * @see #nameToFile(String, URI) */ public static URL nameToURL(String name, URI baseDirectory, ClassLoader classLoader) throws IOException { if ((name == null) || name.trim().equals("")) { return null; } // If the name begins with "$CLASSPATH", or // "xxxxxxCLASSPATHxxxxxx",then attempt to open the file // relative to the classpath. // NOTE: Use the dummy variable constant set up in the constructor. if (name.startsWith(_CLASSPATH_VALUE) || name.startsWith("$CLASSPATH")) { // Try relative to classpath. String classpathKey; if (name.startsWith(_CLASSPATH_VALUE)) { classpathKey = _CLASSPATH_VALUE; } else { classpathKey = "$CLASSPATH"; } String trimmedName = name.substring(classpathKey.length() + 1); if (classLoader == null) { String referenceClassName = "ptolemy.util.FileUtilities"; try { // WebStart: We might be in the Swing Event thread, so // Thread.currentThread().getContextClassLoader() // .getResource(entry) probably will not work so we // use a marker class. Class<?> referenceClass = Class.forName(referenceClassName); classLoader = referenceClass.getClassLoader(); } catch (Exception ex) { // IOException constructor does not take a cause IOException ioException = new IOException("Cannot look up class \"" + referenceClassName + "\" or get its ClassLoader."); ioException.initCause(ex); throw ioException; } } // Use Thread.currentThread()... for Web Start. URL result = classLoader.getResource(trimmedName); if (result == null) { throw new IOException("Cannot find file '" + trimmedName + "' in classpath"); } return result; } File file = new File(name); if (file.isAbsolute()) { if (!file.canRead()) { // FIXME: This is a hack. // Expanding the configuration with Ptolemy II installed // in a directory with spaces in the name fails on // JAIImageReader because PtolemyII.jpg is passed in // to this method as C:\Program%20Files\Ptolemy\... file = new File(StringUtilities.substitute(name, "%20", " ")); URL possibleJarURL = null; if (!file.canRead()) { // ModelReference and FilePortParameters sometimes // have paths that have !/ in them. possibleJarURL = ClassUtilities.jarURLEntryResource(name); if (possibleJarURL != null) { file = new File(possibleJarURL.getFile()); } } if (!file.canRead()) { throw new IOException("Cannot read file '" + name + "' or '" + StringUtilities.substitute(name, "%20", " ") + "'" + ((possibleJarURL == null) ? "" : (" or '" + possibleJarURL.getFile() + ""))); } } return file.toURI().toURL(); } else { // Try relative to the base directory. if (baseDirectory != null) { // Try to resolve the URI. URI newURI; try { newURI = baseDirectory.resolve(name); } catch (Exception ex) { // FIXME: Another hack // This time, if we try to open some of the JAI // demos that have actors that have defaults FileParameters // like "$PTII/doc/img/PtolemyII.jpg", then resolve() // bombs. String name2 = StringUtilities.substitute(name, "%20", " "); try { newURI = baseDirectory.resolve(name2); name = name2; } catch (Exception ex2) { IOException io = new IOException("Problem with URI format in '" + name + "'. " + "and '" + name2 + "' " + "This can happen if the file name " + "is not absolute" + "and is not present relative to the " + "directory in which the specified model " + "was read (which was '" + baseDirectory + "')"); io.initCause(ex2); throw io; } } String urlString = newURI.toString(); try { // Adding another '/' for remote execution. if ((newURI.getScheme() != null) && (newURI.getAuthority() == null)) { urlString = urlString.substring(0, 6) + "//" + urlString.substring(6); // } else { // urlString = urlString.substring(0, 6) + "/" // + urlString.substring(6); } return new URL(urlString); } catch (Exception ex3) { try { // Under Webstart, opening // hoc/demo/ModelReference/ModelReference.xml // requires this because the URL is relative. return new URL(baseDirectory.toURL(), urlString); } catch (Exception ex4) { try { // Under Webstart, ptalon, EightChannelFFT // requires this. return new URL(baseDirectory.toURL(), newURI.toString()); } catch (Exception ex5) { // Ignore } IOException io = new IOException("Problem with URI format in '" + urlString + "'. " + "This can happen if the '" + urlString + "' is not absolute" + " and is not present relative to the directory" + " in which the specified model was read" + " (which was '" + baseDirectory + "')"); io.initCause(ex3); throw io; } } } // As a last resort, try an absolute URL. URL url = new URL(name); // If we call new URL("http", null, /foo); // then we get "http:/foo", which should be "http://foo" // This change suggested by Dan Higgins and Kevin Kruland // See kepler/src/util/URLToLocalFile.java try { String fixedURLAsString = url.toString().replaceFirst("(https?:)//?", "$1//"); url = new URL(fixedURLAsString); } catch (Exception e) { // Ignore } return url; } } /** * Open the specified file for reading. If the specified name is * "System.in", then a reader from standard in is returned. If the name * begins with "$CLASSPATH" or "xxxxxxCLASSPATHxxxxxx", then the name is * passed to {@link #nameToURL(String, URI, ClassLoader)} If the file name * is not absolute, the it is assumed to be relative to the specified base * URI. * * @see #nameToURL(String, URI, ClassLoader) * @param name * File name. * @param base * The base URI for relative references. * @param classLoader * The class loader to use to locate system resources, or null to * use the system class loader that was used to load this class. * @return If the name is null or the empty string, then null is returned, * otherwise a buffered reader is returned. * * @exception IOException * If the file cannot be opened. */ public static BufferedReader openForReading(String name, URI base, ClassLoader classLoader) throws IOException { if ((name == null) || name.trim().equals("")) { return null; } if (name.trim().equals("System.in")) { if (STD_IN == null) { STD_IN = new BufferedReader(new InputStreamReader(System.in)); } return STD_IN; } // Not standard input. Try URL mechanism. URL url = nameToURL(name, base, classLoader); if (url == null) { throw new IOException("Could not convert \"" + name + "\" with base \"" + base + "\" to a URL."); } InputStreamReader inputStreamReader = null; try { inputStreamReader = new InputStreamReader(url.openStream()); } catch (IOException ex) { // Try it as a jar url. // WebStart ptalon MapReduce needs this. try { URL possibleJarURL = ClassUtilities.jarURLEntryResource(url.toString()); if (possibleJarURL != null) { inputStreamReader = new InputStreamReader(possibleJarURL.openStream()); } // If possibleJarURL is null, this throws an exception, // which we ignore and report the first exception (ex) return new BufferedReader(inputStreamReader); } catch (Exception ex2) { try { if (inputStreamReader != null) { inputStreamReader.close(); } } catch (IOException ex3) { // Ignore } IOException ioException = new IOException("Failed to open \"" + url + "\"."); ioException.initCause(ex); throw ioException; } } return new BufferedReader(inputStreamReader); } /** * Open the specified file for writing or appending. If the specified name * is "System.out", then a writer to standard out is returned; otherwise, * pass the name and base to {@link #nameToFile(String, URI)} and create a * file writer. If the file does not exist, then create it. If the file name * is not absolute, the it is assumed to be relative to the specified base * directory. If permitted, this method will return a Writer that will * simply overwrite the contents of the file. It is up to the user of this * method to check whether this is OK (by first calling * {@link #nameToFile(String, URI)} and calling exists() on the returned * value). * * @param name * File name. * @param base * The base URI for relative references. * @param append * If true, then append to the file rather than overwriting. * @return If the name is null or the empty string, then null is returned, * otherwise a writer is returned. * @exception IOException * If the file cannot be opened or created. */ public static Writer openForWriting(String name, URI base, boolean append) throws IOException { if ((name == null) || name.trim().equals("")) { return null; } if (name.trim().equals("System.out")) { if (STD_OUT == null) { STD_OUT = new PrintWriter(System.out); } return STD_OUT; } File file = nameToFile(name, base); return new FileWriter(file, append); } // ///////////////////////////////////////////////////////////////// // // public members //// /** * Standard in as a reader, which will be non-null only after a call to * openForReading("System.in"). */ public static BufferedReader STD_IN = null; /** * Standard out as a writer, which will be non-null only after a call to * openForWriting("System.out"). */ public static PrintWriter STD_OUT = null; // ///////////////////////////////////////////////////////////////// // // private methods //// /** * Copy files safely. If there are problems, the streams are close * appropriately. * * @param inputStream * The input stream. * @param destinationFile * The destination File. * @exception IOException * If the input stream cannot be created or read, or * if * there is a problem writing to the destination file. */ private static void _binaryCopyStream(InputStream inputStream, File destinationFile) throws IOException { // Copy the source file. BufferedInputStream input = null; try { input = new BufferedInputStream(inputStream); BufferedOutputStream output = null; try { File parent = destinationFile.getParentFile(); if (parent != null && !parent.exists()) { if (!parent.mkdirs()) { throw new IOException("Failed to create directories " + "for \"" + parent + "\"."); } } output = new BufferedOutputStream(new FileOutputStream(destinationFile)); int c; while ((c = input.read()) != -1) { output.write(c); } } finally { if (output != null) { try { output.close(); } catch (Throwable throwable) { throw new RuntimeException(throwable); } } } } finally { if (input != null) { try { input.close(); } catch (Throwable throwable) { throw new RuntimeException(throwable); } } } } // ///////////////////////////////////////////////////////////////// // // private members //// /** * Tag value used by this class and registered as a parser constant for the * identifier "CLASSPATH" to indicate searching in the classpath. This is a * hack, but it deals with the fact that Java is not symmetric in how it * deals with getting files from the classpath (using getResource) and * getting files from the file system. */ private static String _CLASSPATH_VALUE = "xxxxxxCLASSPATHxxxxxx"; }