All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.linkedin.groovy.util.io.GroovyIOUtils.groovy Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2010-2010 LinkedIn, Inc
 * Portions Copyright (c) 2011 Yan Pujante
 *
 * 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.linkedin.groovy.util.io

import org.linkedin.util.io.resource.Resource
import org.linkedin.util.io.IOUtils
import org.linkedin.util.io.PathUtils
import org.linkedin.groovy.util.net.GroovyNetUtils
import org.linkedin.groovy.util.ant.AntUtils
import org.linkedin.groovy.util.lang.GroovyLangUtils

/**
 * IO related utilities
 *
 * @author [email protected]
 */
class GroovyIOUtils extends IOUtils
{
  /**
   * returns a file... handles File, URI, URL, string, null
   */
  static File toFile(s)
  {
    toFile(s, null)
  }

  /**
   * returns a file... handles File, URI, URL, string, null
   * @param tempFolder if the file is not local, where to store it temporarilly (null
   *                   means standard java vm temp folder)
   */
  static File toFile(s, File tempFolder)
  {
    (File) toFileWithTempStatus(s, tempFolder).file
  }

  /**
   * handles File, URI, URL, string, null
   * returns a map with file and tempStatus (boolean)
   * @param tempFolder if the file is not local, where to store it temporarilly (null
   *                   means standard java vm temp folder)
   */
  static Map toFileWithTempStatus(s, File tempFolder)
  {
    if(s == null)
      return [file: null, tempStatus: false]

    if(s instanceof File)
      return [file: s, tempStatus: false]

    if(s instanceof Resource)
      return [file: s.file, tempStatus: false]

    if(s instanceof String || s instanceof GString)
    {
      def uri
      try
      {
        uri = new URI(s)
      }
      catch(URISyntaxException e)
      {
        // ok will handle below
      }

      if(!uri?.scheme)
      {
        return [file: new File(s), tempStatus: false]
      }
    }

    URI uri = GroovyNetUtils.toURI(s)

    if(uri.scheme == 'file')
      return [file: new File(uri.path), tempStatus: false]

    // this is not a local file => make it local...
    File tempFile = AntUtils.tempFile(destdir: tempFolder, prefix: 'toFile')

    tempFile.withOutputStream { out ->
      uri.toURL().withInputStream { ins ->
        out << ins
      }
    }

    return [file: tempFile, tempStatus: true]
  }

  /**
   * Will convert s into a File object (if possible) and call
   * the closure with it. Once the closure is over, if the file was not local (and as a result was
   * copied locally for the duration of the closure) it will be deleted. As a result the closure
   * should *not* store the File object around for later use or assume that the
   * underlying file will still exist at a later point. Use {@link #toFile(Object)} instead if
   * this is what you want to do.
   * 
   * @param s handles File, URI, URL, string, null
   * @param closure will be called back with a File object (or null)
   * @return whatever the closure returns
   */
  static def withFile(s, Closure closure)
  {
    Map m = toFileWithTempStatus(s, null)

    try
    {
      return closure(m.file)
    }
    finally
    {
      if(m.tempStatus)
        IOUtils.deleteFile((File) m.file)
    }
  }

  /**
   * true if child is really a child of parent or in other words if child
   * is located in a subpath of parent (handle canonical path properly)
   */
  static boolean isChild(File parent, File child)
  {
    if(child == null || parent == null)
      return false

    return child.canonicalPath.startsWith(parent.canonicalPath)
  }

  /**
   * Ex: parent=/a/b/c child='/a/b/c/d/e'... would return d/e
   * @return a file object which contains the relative part from the child to the parent.
   * null if child is not a child of parent! If child is relative the return child
   */
  static File makeRelativeToParent(File parent, File child)
  {
    if(child == null || parent == null)
      return null

    if(!child.isAbsolute())
      return child

    def puri = parent.toURI().normalize()
    def curi = child.toURI().normalize()

    def relative = puri.relativize(curi)

    if(relative.isAbsolute())
    {
      // this means that the child is not relative to the parent
      return null
    }
    else
    {
      return new File(relative.path)
    }
  }

  /**
   * The difference between reader.eachLine() and this method is that
   * as soon as the closure returns false then the iteration is stopped. There is no
   * way to stop the iteration with reader.eachLine().
   */
  static void eachLine(Reader reader, Closure closure)
  {
    if(!(reader instanceof BufferedReader))
    {
      reader = new BufferedReader(reader)
    }

    String line = null

    while((line = reader.readLine()) != null)
    {
      if(!closure(line))
        return
    }
  }

  /**
   * Convenient call which calls {@link #eachLine(Reader, Closure).
   */
  static void eachLine(URL url, Closure closure)
  {
    url.withReader { reader ->
      eachLine(reader, closure)
    }
  }

  /**
   * Every child resource of this resource (recursively) is being passed to the closure. If the
   * closure returns true then it will be part of the result.
   */
  static def findAll(Resource resource, Closure closure)
  {
    def matchingResources = []

    eachChildRecurse(resource) { Resource r ->
      if(closure(r))
        matchingResources << r
    }

    return matchingResources
  }

  /**
   * The closure will be called for every child (recursively) of the provided resource
   * @return the resource passed it
   */
  static Resource eachChildRecurse(Resource resource, Closure closure)
  {
    def dirs = []

    resource.list().each { Resource r ->
      closure(r)
      if(r.isDirectory())
        dirs << r
    }

    dirs.each { eachChildRecurse(it, closure) }

    return resource
  }

  /**
   * Creates the directory and parents of the provided directory. Returns dir.
   */
  static File mkdirs(File dir)
  {
    if(dir)
      return AntUtils.mkdirs(dir)
    else
      return null
  }

  /**
   * This convenient call takes a file you want to (over)write to and a closure. The closure is
   * called back with another file in the same folder that you can write to and then rename the file
   * to the one you wanted. The fact that it is in the same folder ensures that the rename should
   * be quick and not really require any copy thus is less likely to fail. If the rename fails it
   * throws an exception, thus ensuring that if there was an original file it won't be in a partial
   * state.
   *
   * Note that this method automatically creates the parent folders if they don't exist.
   *
   * @param toFile the final file where you want your output to be
   * @param closure takes a File as a parameter that you should use
   * @return whatever the closure returns
   */
  static def safeOverwrite(File toFile, Closure closure)
  {
    safeOverwrite(toFile,
                  { File f ->
                    new File(f.parentFile,
                             "++tmp.${f.name}.${UUID.randomUUID().toString()}.tmp++")},
                  closure)
  }

  /**
   * This variant takes a tempFileFactory if you want to control precisely where
   * and how the temporary file is created (note that if the tempoary file is not created in the
   * same folder as toFile then the rename operation may actually be a copy/delete
   * instead of just a rename thus defeating the purpose of this method!)
   *
   * @param toFile the final file where you want your output to be
   * @param tempFileFactory closure which takes toFile as an argument and returns
   *        a temporary file
   * @param closure takes a File as a parameter that you should use
   * @return whatever the closure returns
   */
  static def safeOverwrite(File toFile, Closure tempFileFactory, Closure closure)
  {
    if(toFile == null)
      return null

    mkdirs(toFile.parentFile)

    File newFile = tempFileFactory(toFile)

    try
    {
      def res = closure(newFile)

      if(newFile.exists() && !newFile.renameTo(toFile))
      {
        // somehow the rename operation did not work => delete new file (will happen in the finally)
        // and throw an exception thus effectively leaving the file system in the same state as
        // when the method was called
        throw new IOException("Unable to rename ${newFile} to ${toFile}")
      }

      return res
    }
    finally
    {
      // always deleting the newFile in the finally ensuring that the filesystem remain in
      // the same state as when enterred
      GroovyLangUtils.noExceptionWithMessage(toFile) {
        deleteFile(newFile)
      }
    }
  }

  /**
   * Fetches the file pointed to by the location. The location can be File,
   * a String or URI and must contain a scheme. Example of locations:
   * http://locahost:8080/file.txt', file:/tmp/file.txt,
   * ivy:/org.linkedin/util-core/1.0.0.
   *
   * @param location where is the content you want to retrieve locally
   * @param destination where to store the content locally
   * @return destination
   */
  static File fetchContent(location, File destination) throws IOException
  {
    URI uri = GroovyNetUtils.toURI(location)

    if(uri == null)
      throw new FileNotFoundException(location.toString())

    // See http://download.oracle.com/javase/1.4.2/docs/api/java/net/URI.html#getUserInfo()
    def params = [src: uri, dest: destination]

    // Extract the user:pass if it exists
    def userInfo = uri.userInfo
    if(userInfo)
    {
      userInfo = userInfo.split(":")
      if(userInfo.length == 2)
      {
        params.username = userInfo[0]
        params.password = userInfo[1]
      }
    }

    try
    {
      AntUtils.withBuilder { ant ->
        ant.get(params)
      }
    }
    catch(IOException e)
    {
      throw e
    }
    catch(Exception e)
    {
      IOException ioe = new FileNotFoundException(location?.toString())
      ioe.initCause(e)
      throw ioe
    }

    return destination
  }

  /**
   * Returns the content of the location as a String
   *
   * @throws IOException if the file is not reachable
   */
  static String cat(location) throws IOException
  {
    File tempFile = File.createTempFile('GroovyIOUtils.cat', '.txt')

    try
    {
      fetchContent(location, tempFile)
      return tempFile.text
    }
    finally
    {
      tempFile.delete()
    }
  }

  protected GroovyIOUtils()
  {
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy