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

org.basex.query.func.FNFile Maven / Gradle / Ivy

There is a newer version: 11.6
Show newest version
package org.basex.query.func;

import static org.basex.query.func.Function.*;
import static org.basex.query.util.Err.*;
import static org.basex.util.Token.*;

import java.io.*;
import java.nio.charset.*;
import java.util.regex.*;

import org.basex.core.*;
import org.basex.io.*;
import org.basex.io.out.*;
import org.basex.io.serial.*;
import org.basex.query.*;
import org.basex.query.expr.*;
import org.basex.query.iter.*;
import org.basex.query.util.*;
import org.basex.query.value.item.*;
import org.basex.query.value.type.*;
import org.basex.util.*;
import org.basex.util.list.*;

/**
 * Functions on files and directories.
 *
 * @author BaseX Team 2005-12, BSD License
 * @author Rositsa Shadura
 * @author Christian Gruen
 */
public final class FNFile extends StandardFunc {
  /** Line separator. */
  private static final byte[] NL = Token.token(Prop.NL);

  /**
   * Constructor.
   * @param ii input info
   * @param f function definition
   * @param e arguments
   */
  public FNFile(final InputInfo ii, final Function f, final Expr... e) {
    super(ii, f, e);
  }

  @Override
  public Iter iter(final QueryContext ctx) throws QueryException {
    checkCreate(ctx);
    final File path = file(0, ctx);
    switch(sig) {
      case _FILE_LIST:            return list(path, ctx);
      case _FILE_READ_TEXT_LINES: return readTextLines(path, ctx);
      default:                    return super.iter(ctx);
    }
  }

  @Override
  public Item item(final QueryContext ctx, final InputInfo ii) throws QueryException {
    checkCreate(ctx);
    final File path = file(0, ctx);
    try {
      switch(sig) {
        case _FILE_APPEND:            return write(path, true, ctx);
        case _FILE_APPEND_BINARY:     return writeBinary(path, ctx, true);
        case _FILE_APPEND_TEXT:       return writeText(path, true, ctx);
        case _FILE_APPEND_TEXT_LINES: return writeTextLines(path, true, ctx);
        case _FILE_COPY:              return copy(path, ctx, true);
        case _FILE_CREATE_DIR:        return createDirectory(path);
        case _FILE_DELETE:            return delete(path, ctx);
        case _FILE_MOVE:              return copy(path, ctx, false);
        case _FILE_READ_BINARY:       return readBinary(path);
        case _FILE_READ_TEXT:         return readText(path, ctx);
        case _FILE_WRITE:             return write(path, false, ctx);
        case _FILE_WRITE_BINARY:      return writeBinary(path, ctx, false);
        case _FILE_WRITE_TEXT:        return writeText(path, false, ctx);
        case _FILE_WRITE_TEXT_LINES:  return writeTextLines(path, false, ctx);
        case _FILE_PATH_SEPARATOR:    return Str.get(File.pathSeparator);
        case _FILE_DIR_SEPARATOR:     return Str.get(File.separator);
        case _FILE_LINE_SEPARATOR:    return Str.get(NL);
        case _FILE_EXISTS:            return Bln.get(path.exists());
        case _FILE_IS_DIR:            return Bln.get(path.isDirectory());
        case _FILE_IS_FILE:           return Bln.get(path.isFile());
        case _FILE_LAST_MODIFIED:     return lastModified(path);
        case _FILE_SIZE:              return size(path);
        case _FILE_BASE_NAME:         return baseName(path, ctx);
        case _FILE_DIR_NAME:          return dirName(path);
        case _FILE_PATH_TO_NATIVE:    return pathToNative(path);
        case _FILE_RESOLVE_PATH:      return Str.get(path.getAbsolutePath());
        case _FILE_PATH_TO_URI:       return pathToUri(path);
        default:                      return super.item(ctx, ii);
      }
    } catch(final IOException ex) {
      throw FILE_IO.thrw(info, ex);
    }
  }

  /**
   * Returns the last modified date of the specified path.
   * @param path file to be deleted
   * @return result
   * @throws QueryException query exception
   */
  private Item lastModified(final File path) throws QueryException {
    if(!path.exists()) FILE_WHICH.thrw(info, path.getAbsolutePath());
    return new Dtm(path.lastModified(), info);
  }

  /**
   * Returns the size of the specified path.
   * @param path file to be deleted
   * @return result
   * @throws QueryException query exception
   */
  private Item size(final File path) throws QueryException {
    if(!path.exists()) FILE_WHICH.thrw(info, path.getAbsolutePath());
    if(path.isDirectory()) FILE_DIR.thrw(info, path.getAbsolutePath());
    return Int.get(path.length());
  }

  /**
   * Returns the base name of the specified path.
   * @param path file path
   * @param ctx query context
   * @return result
   * @throws QueryException query exception
   */
  private Str baseName(final File path, final QueryContext ctx) throws QueryException {
    if(path.getPath().isEmpty()) return Str.get(".");
    final String suf = expr.length < 2 ? null : string(checkStr(expr[1], ctx));
    String pth = path.getName();
    if(suf != null && pth.endsWith(suf))
      pth = pth.substring(0, pth.length() - suf.length());
    return Str.get(pth);
  }

  /**
   * Returns the dir name of the specified path.
   * @param path file path
   * @return result
   */
  private static Str dirName(final File path) {
    final String pth = path.getParent();
    return Str.get(pth == null ? "." : pth);
  }

  /**
   * Returns the native name of the specified path.
   * @param path file path
   * @return result
   * @throws QueryException query exception
   */
  private Str pathToNative(final File path) throws QueryException {
    try {
      return Str.get(path.getCanonicalFile());
    } catch(final IOException ex) {
      throw FILE_PATH.thrw(info, path);
    }
  }

  /**
   * Transforms a file system path into a URI with the file:// scheme.
   * @param path file path
   * @return result
   */
  private static Uri pathToUri(final File path) {
    return Uri.uri(token(path.toURI().toString()));
  }

  /**
   * Lists all files in a directory.
   * @param path root directory
   * @param ctx query context
   * @return iterator
   * @throws QueryException query exception
   */
  private Iter list(final File path, final QueryContext ctx) throws QueryException {
    // get canonical representation to resolve symbolic links
    final File dir;
    try {
      dir = new File(path.getCanonicalPath().replaceAll("[\\\\/]$", ""));
    } catch(final IOException ex) {
      throw FILE_PATH.thrw(info, path);
    }

    // check if the addresses path is a directory
    if(!dir.isDirectory()) FILE_NODIR.thrw(info, dir);

    final boolean rec = optionalBool(1, ctx);
    final Pattern pat = expr.length != 3 ? null :
      Pattern.compile(IOFile.regex(string(checkStr(expr[2], ctx))),
        Prop.WIN ? Pattern.CASE_INSENSITIVE : 0);

    final StringList list = new StringList();
    list(dir.getPath().length() + 1, dir, list, rec, pat);

    return new Iter() {
      int c;
      @Override
      public Item next() {
        return c < list.size() ? Str.get(list.get(c++)) : null;
      }
    };
  }

  /**
   * Collects the sub-directories and files of the specified directory.
   * @param root length of root path
   * @param dir root directory
   * @param list file list
   * @param rec recursive flag
   * @param pat file name pattern; ignored if {@code null}
   * @throws QueryException query exception
   */
  private void list(final int root, final File dir, final StringList list,
      final boolean rec, final Pattern pat) throws QueryException {

    // skip invalid directories
    final File[] ch = dir.listFiles();
    if(ch == null) return;

    // parse directories
    if(rec) {
      for(final File f : ch) {
        if(!mayBeLink(f) && f.isDirectory()) list(root, f, list, rec, pat);
      }
    }
    // parse files. ignore directories if a pattern is specified
    for(final File f : ch) {
      if(pat == null || pat.matcher(f.getName()).matches() && !f.isDirectory()) {
        list.add(f.getPath().substring(root));
      }
    }
  }

  /**
   * Checks if the specified file may be a symbolic link.
   * @param f file to check
   * @return result
   * @throws QueryException query exception
   */
  private boolean mayBeLink(final File f) throws QueryException {
    try {
      final String p1 = f.getAbsolutePath();
      final String p2 = f.getCanonicalPath();
      return !(Prop.WIN ? p1.equalsIgnoreCase(p2) : p1.equals(p2));
    } catch(final IOException ex) {
      throw FILE_PATH.thrw(info, f);
    }
  }

  /**
   * Creates a directory.
   * @param path directory to be created
   * @return result
   * @throws QueryException query exception
   */
  private synchronized Item createDirectory(final File path) throws QueryException {
    // resolve symbolic links
    File f;
    try {
      f = path.getCanonicalFile();
    } catch(final IOException ex) {
      throw FILE_PATH.thrw(info, path);
    }

    // find lowest existing path
    while(!f.exists()) {
      f = f.getParentFile();
      if(f == null) throw FILE_PATH.thrw(info, path);
    }
    // warn if lowest path points to a file
    if(f.isFile()) FILE_EXISTS.thrw(info, path);

    // only create directories if path does not exist yet
    if(!path.exists() && !path.mkdirs()) FILE_CREATE.thrw(info, path);
    return null;
  }

  /**
   * Deletes a file or directory.
   * @param path file to be deleted
   * @param ctx query context
   * @return result
   * @throws QueryException query exception
   */
  private synchronized Item delete(final File path, final QueryContext ctx)
      throws QueryException {

    if(!path.exists()) FILE_WHICH.thrw(info, path.getAbsolutePath());
    if(optionalBool(1, ctx)) {
      deleteRec(path);
    } else if(!path.delete()) {
      throw (path.isDirectory() ? FILE_NEDIR : FILE_DEL).thrw(info, path);
    }
    return null;
  }

  /**
   * Recursively deletes a file path.
   * @param path path to be deleted
   * @throws QueryException query exception
   */
  private synchronized void deleteRec(final File path) throws QueryException {
    final File[] ch = path.listFiles();
    if(ch != null) for(final File f : ch) deleteRec(f);
    if(!path.delete()) FILE_DEL.thrw(info, path);
  }

  /**
   * Reads the contents of a binary file.
   * @param path input path
   * @return Base64Binary
   * @throws QueryException query exception
   */
  private B64Stream readBinary(final File path) throws QueryException {
    if(!path.exists()) FILE_WHICH.thrw(info, path.getAbsolutePath());
    if(path.isDirectory()) FILE_DIR.thrw(info, path.getAbsolutePath());
    return new B64Stream(new IOFile(path), FILE_IO);
  }

  /**
   * Reads the contents of a file.
   * @param path input path
   * @param ctx query context
   * @return string
   * @throws QueryException query exception
   */
  private StrStream readText(final File path, final QueryContext ctx)
      throws QueryException {

    final String enc = encoding(1, FILE_ENCODING, ctx);
    if(!path.exists()) FILE_WHICH.thrw(info, path.getAbsolutePath());
    if(path.isDirectory()) FILE_DIR.thrw(info, path.getAbsolutePath());
    return new StrStream(new IOFile(path), enc, FILE_IO);
  }

  /**
   * Returns the contents of a file line by line.
   * @param path input path
   * @param ctx query context
   * @return string
   * @throws QueryException query exception
   */
  private Iter readTextLines(final File path, final QueryContext ctx)
      throws QueryException {
    return FNGen.textIter(readText(path, ctx).string(info));
  }

  /**
   * Writes items to a file.
   * @param path file to be written
   * @param append append flag
   * @param ctx query context
   * @return true if file was successfully written
   * @throws QueryException query exception
   * @throws IOException I/O exception
   */
  private synchronized Item write(final File path, final boolean append,
      final QueryContext ctx) throws QueryException, IOException {

    check(path);
    final Iter ir = expr[1].iter(ctx);
    final SerializerProp sp = FuncParams.serializerProp(
        expr.length > 2 ? expr[2].item(ctx, info) : null);

    final PrintOutput out = PrintOutput.get(new FileOutputStream(path, append));
    try {
      final Serializer ser = Serializer.get(out, sp);
      for(Item it; (it = ir.next()) != null;) ser.serialize(it);
      ser.close();
    } catch(final SerializerException ex) {
      throw ex.getCause(info);
    } finally {
      out.close();
    }
    return null;
  }

  /**
   * Writes items to a file.
   * @param path file to be written
   * @param append append flag
   * @param ctx query context
   * @return true if file was successfully written
   * @throws QueryException query exception
   * @throws IOException I/O exception
   */
  private synchronized Item writeText(final File path, final boolean append,
      final QueryContext ctx) throws QueryException, IOException {

    check(path);
    final byte[] s = checkStr(expr[1], ctx);
    final String enc = encoding(2, FILE_ENCODING, ctx);
    final Charset cs = enc == null || enc == UTF8 ? null : Charset.forName(enc);

    final PrintOutput out = PrintOutput.get(new FileOutputStream(path, append));
    try {
      out.write(cs == null ? s : Token.string(s).getBytes(cs));
    } finally {
      out.close();
    }
    return null;
  }

  /**
   * Writes items to a file.
   * @param path file to be written
   * @param append append flag
   * @param ctx query context
   * @return true if file was successfully written
   * @throws QueryException query exception
   * @throws IOException I/O exception
   */
  private synchronized Item writeTextLines(final File path, final boolean append,
      final QueryContext ctx) throws QueryException, IOException {

    check(path);
    final Iter ir = expr[1].iter(ctx);
    final String enc = encoding(2, FILE_ENCODING, ctx);
    final Charset cs = enc == null || enc == UTF8 ? null : Charset.forName(enc);

    final PrintOutput out = PrintOutput.get(new FileOutputStream(path, append));
    try {
      for(Item it; (it = ir.next()) != null;) {
        final Type ip = it.type;
        if(!ip.isString() && !ip.isUntyped()) Err.type(this, AtomType.STR, it);
        final byte[] s = it.string(info);
        out.write(cs == null ? s : Token.string(s).getBytes(cs));
        out.write(cs == null ? NL : Prop.NL.getBytes(cs));
      }
    } finally {
      out.close();
    }
    return null;
  }

  /**
   * Writes binary items to a file.
   * @param path file to be written
   * @param ctx query context
   * @param append append flag
   * @return result
   * @throws QueryException query exception
   * @throws IOException I/O exception
   */
  private synchronized Item writeBinary(final File path, final QueryContext ctx,
      final boolean append) throws QueryException, IOException {

    check(path);
    final Iter ir = expr[1].iter(ctx);
    final BufferOutput out = new BufferOutput(new FileOutputStream(path, append));
    try {
      for(Item it; (it = ir.next()) != null;) {
        if(!(it instanceof Bin)) BINARYTYPE.thrw(info, it.type);
        final InputStream is = it.input(info);
        try {
          for(int i; (i = is.read()) != -1;)  out.write(i);
        } finally {
          is.close();
        }
      }
    } finally {
      out.close();
    }
    return null;
  }

  /**
   * Checks the target directory of the specified file.
   * @param path file to be written
   * @throws QueryException query exception
   */
  private void check(final File path) throws QueryException {
    final IOFile io = new IOFile(path);
    if(io.isDir()) FILE_DIR.thrw(info, io);
    final IOFile dir = new IOFile(io.dir());
    if(!dir.exists()) FILE_NODIR.thrw(info, dir);
  }

  /**
   * Transfers a file path, given a source and a target.
   * @param src source file to be copied
   * @param ctx query context
   * @param copy copy flag (no move)
   * @return result
   * @throws QueryException query exception
   * @throws IOException I/O exception
   */
  private synchronized Item copy(final File src, final QueryContext ctx,
      final boolean copy) throws QueryException, IOException {

    File trg = file(1, ctx).getAbsoluteFile();
    if(!src.exists()) FILE_WHICH.thrw(info, src.getAbsolutePath());

    if(trg.isDirectory()) {
      // target is a directory: attach file name
      trg = new File(trg, src.getName());
      if(trg.isDirectory()) FILE_DIR.thrw(info, trg);
    } else if(!trg.isFile()) {
      // target does not exist: ensure that parent exists
      if(!trg.getParentFile().isDirectory()) FILE_NODIR.thrw(info, trg);
    } else if(src.isDirectory()) {
      // if target is file, source cannot be a directory
      FILE_DIR.thrw(info, src);
    }

    // ignore operations on same source and target path
    if(!src.equals(trg)) {
      if(copy) copy(src, trg);
      else if(!src.renameTo(trg)) FILE_MOVE.thrw(info, src, trg);
    }
    return null;
  }

  /**
   * Recursively copies files.
   * @param src source path
   * @param trg target path
   * @throws QueryException query exception
   * @throws IOException I/O exception
   */
  private synchronized void copy(final File src, final File trg)
      throws QueryException, IOException {

    if(src.isDirectory()) {
      if(!trg.mkdir()) FILE_CREATE.thrw(info, trg);
      final File[] files = src.listFiles();
      if(files == null) FILE_LIST.thrw(info, src);
      for(final File f : files) copy(f, new File(trg, f.getName()));
    } else {
      new IOFile(src).copyTo(new IOFile(trg));
    }
  }

  /**
   * Returns the value of an optional boolean.
   * @param i argument index
   * @param ctx query context
   * @return boolean value
   * @throws QueryException query exception
   */
  private boolean optionalBool(final int i, final QueryContext ctx)
      throws QueryException {
    return i < expr.length && checkBln(expr[i], ctx);
  }

  /**
   * Converts the specified argument to a file instance.
   * @param i argument index
   * @param ctx query context
   * @return file instance
   * @throws QueryException query exception
   */
  private File file(final int i, final QueryContext ctx) throws QueryException {
    return i >= expr.length ? null :
      new IOFile(IOUrl.file(string(checkStr(expr[i], ctx)))).file();
  }

  @Override
  public boolean uses(final Use u) {
    return u == Use.NDT && !oneOf(sig, _FILE_BASE_NAME, _FILE_DIR_NAME,
        _FILE_DIR_SEPARATOR, _FILE_PATH_SEPARATOR, _FILE_PATH_TO_NATIVE,
        _FILE_PATH_TO_URI, _FILE_RESOLVE_PATH) || super.uses(u);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy