/* * Eoulsan development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public License version 2.1 or * later and CeCILL-C. This should be distributed with the code. * If you do not have a copy, see: * * http://www.gnu.org/licenses/lgpl-2.1.txt * http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt * * Copyright for this code is held jointly by the Genomic platform * of the Institut de Biologie de l'École normale supérieure and * the individual authors. These should be listed in @author doc * comments. * * For more information on the Eoulsan project and its aims, * or to join the Eoulsan Google group, visit the home page * at: * * http://outils.genomique.biologie.ens.fr/eoulsan * */ package fr.ens.biologie.genomique.eoulsan.data; import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import fr.ens.biologie.genomique.eoulsan.data.protocols.DataProtocol; import fr.ens.biologie.genomique.eoulsan.data.protocols.DataProtocolService; import fr.ens.biologie.genomique.eoulsan.io.CompressionType; import fr.ens.biologie.genomique.eoulsan.util.StringUtils; /** * This class define a DataFile. * @since 1.0 * @author Laurent Jourdren */ public class DataFile implements Comparable<DataFile>, Serializable { private static final long serialVersionUID = -3280343485491150872L; /** The separator char ('/'). */ public static final char separatorChar = '/'; /** The separator char ('/') as a String. */ public static final String separator = "" + separatorChar; private String src; private String name; private String protocolPrefixInSource; private DataProtocol protocol; private DataFileMetadata md; private String unknownProtocolName; // // Getters // /** * Get the source of this DataFile. * @return a String with the source of this DataFile */ public String getSource() { return this.src; } /** * Get the name of this DataFile. * @return a String with the name of this DataFile */ public String getName() { return this.name; } /** * Get the base name of this DataFile without all its extensions. The result * is computed with the output of the getName() method. * @return a String with the base name of this DataFile */ public String getBasename() { return StringUtils.basename(getName()); } /** * Get the extension of this DataFile without compression extension. The * result is computed with the output of the getName() method. * @return a String with the extension of this DataFile. The result String is * empty if there is no extension */ public String getExtension() { return StringUtils.extensionWithoutCompressionExtension(getName()); } /** * Get the base name of this DataFile with all its extensions (include * compression extension). The result is computed with the output of the * getName() method. * @return a String with the base name of this DataFile. The result String is * empty if there is no extension */ public String getFullExtension() { return StringUtils.extension(getName()); } /** * Get the compression extension of this DataFile. The result is computed with * the output of the getName() method. * @return a String with the compression extension of this DataFile. The * result String is empty if there is no compression extension */ public String getCompressionExtension() { return StringUtils.compressionExtension(getName()); } /** * Get the compression Type of this DataFile. The result is computed with the * output of the getName() method. * @return a CompressionType object */ public CompressionType getCompressionType() { return CompressionType.getCompressionTypeByFilename(getName()); } /** * Get the DataFormat of the DataFile. The result is computed with the output * of the getName() method. This is an alias for getDataFormatFromFilename() * of the DataFormatRegistry. * @return the DataFormat of the DataFile */ public DataFormat getDataFormat() { return DataFormatRegistry.getInstance() .getDataFormatFromFilename(getName()); } /** * Get the parent of this DataFile. * @return the parent DataFile * @throws IOException if an error occurs while the parent */ public DataFile getParent() throws IOException { return getProtocol().getDataFileParent(this); } /** * Get the protocol of this DataFile. * @return a DataProtocol */ public DataProtocol getProtocol() throws IOException { if (this.protocol == null) { throw new IOException("Unknown protocol: " + this.unknownProtocolName); } return this.protocol; } /** * Get the metadata for this DataFile. * @return a DataFileMetadata with all metadata information about this * DataFile * @throws IOException if the protocol is unknown or if the DataFile does not * exists */ public DataFileMetadata getMetaData() throws IOException { if (this.md == null) { this.md = getProtocol().getMetadata(this); } return this.md; } /** * Get the prefix of the protocol in the source name of the DataFile. * @return the prefix of the protocol */ public String getProtocolPrefixInSource() { return this.protocolPrefixInSource; } /** * Test if the DataFile use the defaultProtocol. * @return true if the DataFile use the default protocol */ public boolean isLocalFile() { try { return DataProtocolService.getInstance().getDefaultProtocol() .equals(getProtocol()); } catch (IOException e) { return false; } } /** * Convert the DataFile object to File object if the underlying protocol allow * it. Only local protocol can return a value. * @return a File object or null if the underlying protocol does not allow it */ public File toFile() { if (this.protocol == null) { return null; } return this.protocol.getSourceAsFile(this); } /** * Convert the DataFile object to Path object if the underlying protocol allow * it. * @return a Path object or null if the underlying protocol does not allow it */ public Path toPath() { if (this.protocol == null) { return null; } final URI uri = toUri(); if (uri == null) { return null; } return Paths.get(uri); } /** * Convert the DataFile object to an URI. * @return an URI object or null if the DataFile cannot be converted into URI */ public URI toUri() { try { return new URI(this.src); } catch (URISyntaxException e) { return null; } } // // Other methods // /** * Create an OutputStream for the DataFile. If the DataFile is declared as * compressed by its content type or its extension, the output stream will be * automatically compress data. * @return an OutputStream object * @throws IOException if an error occurs while creating the DataFile */ public OutputStream create() throws IOException { final OutputStream os = rawCreate(); final CompressionType ct; final String contentEncoding = this.md == null ? null : this.md.getContentEncoding(); if (contentEncoding != null) { ct = CompressionType.getCompressionTypeByContentEncoding(contentEncoding); } else { ct = CompressionType.getCompressionTypeByFilename(getName()); } if (ct == null) { return os; } return ct.createOutputStream(os); } /** * Create an OutputStream for the DataFile. The output stream will not * automatically compress data. * @return an OutputStream object * @throws IOException if an error occurs while creating the DataFile */ public OutputStream rawCreate() throws IOException { return getProtocol().putData(this, this.md == null ? null : this.md); } /** * Create an InputStream for the DataFile. If the DataFile is compressed, the * input stream will be automatically uncompress. * @return an InputStream object * @throws IOException if an error occurs while opening the DataFile */ public InputStream open() throws IOException { final InputStream is = rawOpen(); final DataFileMetadata md = getMetaData(); final CompressionType ct = CompressionType .getCompressionTypeByContentEncoding(md.getContentEncoding()); if (ct == null) { return is; } return ct.createInputStream(is); } /** * Create an InputStream for the DataFile. The input stream will not * automatically uncompress data. * @return an InputStream object * @throws IOException if an error occurs while opening the DataFile */ public InputStream rawOpen() throws IOException { return getProtocol().getData(this); } /** * Copy this DataFile in a other DataFile. * @param dest destination DataFile * @throws IOException if an error occurs while copying the DataFile */ public void copyTo(final DataFile dest) throws IOException { if (dest == null) { throw new NullPointerException("The destination DataFile is null."); } dest.getProtocol().putData(this, dest); } /** * Check if this DataFile exists. * @return true if this DataFile exists */ public boolean exists() { return exists(true); } /** * Check if this DataFile exists. * @param followLink follow the link target if the file is a symbolic link * @return true if this DataFile exists */ public boolean exists(final boolean followLink) { try { return getProtocol().exists(this, followLink); } catch (IOException e) { return false; } } /** * Create a directory with the path of the DataFile. * @throws IOException if an error occurs while creating the directory */ public void mkdir() throws IOException { if (!getProtocol().canMkdir()) { throw new IOException( "The underlying protocol does not allow creating directories"); } getProtocol().mkdir(this); } /** * Create a directory and its parents if not exists with the path of the * DataFile. * @throws IOException if an error occurs while creating the directory */ public void mkdirs() throws IOException { if (!getProtocol().canMkdir()) { throw new IOException( "The underlying protocol does not allow creating directories"); } getProtocol().mkdirs(this); } /** * Create a symbolic link that target is the current file. * @param link symbolic file * @throws IOException if an error occurs while creating the symbolic link */ public void symlink(final DataFile link) throws IOException { symlink(link, false); } /** * Create a symbolic link that target is the current file. * @param link symbolic file * @param relativize relativize the link target path * @throws IOException if an error occurs while creating the symbolic link */ public void symlink(final DataFile link, final boolean relativize) throws IOException { if (link == null) { throw new NullPointerException("The link can not be null."); } if (!getProtocol().canSymlink()) { throw new IOException( "The underlying protocol does not allow creating symbolic links"); } if (relativize) { final DataFile parent = isLocalFile() ? new DataFile(getParent().toFile().getAbsoluteFile()) : getParent(); final DataFile newTarget = new DataFile( relativize(link.getParent(), parent), this.getName()); getProtocol().symlink(newTarget, link); } else { getProtocol().symlink(this, link); } } /** * Delete the DataFile. * @throws IOException if an error occurs while deleting the DataFile */ public void delete() throws IOException { delete(false); } /** * Delete the DataFile. * @param recursive recursive deletion * @throws IOException if an error occurs while deleting the DataFile */ public void delete(final boolean recursive) throws IOException { if (!getProtocol().canDelete()) { throw new IOException( "The underlying protocol does not allow deleting files"); } getProtocol().delete(this, recursive); } /** * List the content of a directory. * @return a List with the content of the directory * @throws IOException if an error occurs while listing the directory */ public List<DataFile> list() throws IOException { if (!getProtocol().canList()) { throw new IOException( "The underlying protocol does not allow to list a directory"); } return getProtocol().list(this); } /** * Rename the DataFile. * @param dest destination DataFile * @throws IOException if an error occurs while renaming the DataFile */ public void renameTo(final DataFile dest) throws IOException { if (!getProtocol().canRename()) { throw new IOException( "The underlying protocol does not allow to rename files"); } getProtocol().rename(this, dest); } // // Internal methods // /** * Find the protocol for this DataFile. * @param src the Data File source */ private String findProtocol(final String src) { final int len = src.length(); int pos = -1; for (int i = 0; i < len; i++) { int c = src.charAt(i); if (!(Character.isDigit(c) || Character.isLetter(c))) { pos = i; break; } } if (pos == -1) { return null; } if (len <= pos + 1) { return null; } if (src.charAt(pos) == ':' && src.charAt(pos + 1) == '/') { return src.substring(0, pos); } return null; } /** * Parse the source * @param source the source of the DataFile */ private void parseSource(final String source) { // Looking for the protocol this.protocolPrefixInSource = findProtocol(source); final DataProtocolService registry = DataProtocolService.getInstance(); if (this.protocolPrefixInSource == null) { this.protocol = registry.getDefaultProtocol(); } else { this.protocol = registry.newService(this.protocolPrefixInSource); } if (this.protocol == null) { getLogger().severe("Unknown protocol: \"" + this.protocolPrefixInSource + "\", can't set protocol for DataFile."); this.unknownProtocolName = this.protocolPrefixInSource; } // Set the source name this.src = source; final int lastSlashPos = source.lastIndexOf(separatorChar); if (lastSlashPos == -1) { this.name = source; } else { this.name = source.substring(lastSlashPos + 1); } } /** * Relativize two path. * @param f1 first path * @param f2 second path * @return the relative path */ private static DataFile relativize(final DataFile f1, final DataFile f2) { final URI uri1 = f1.toUri(); final URI uri2 = f2.toUri(); return new DataFile(uri1.relativize(uri2)); } // // Object methods overrides // @Override public int compareTo(final DataFile o) { if (o == null) { throw new NullPointerException("argument cannot be null"); } return this.src.compareTo(o.src); } @Override public boolean equals(final Object o) { if (o == this) { return true; } if (!(o instanceof DataFile)) { return false; } final DataFile df = (DataFile) o; return this.src.equals(df.src); } @Override public int hashCode() { return this.src.hashCode(); } @Override public String toString() { return this.src; } // // Serialization methods // /** * Serialize the object. * @param out the object output stream * @throws IOException if an error occurs while serializing the object */ private void writeObject(final ObjectOutputStream out) throws IOException { out.writeObject(this.src); } /** * Deserialize the object. * @param in the object input stream * @throws IOException if an error occurs while deserializing the object */ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { final String source = (String) in.readObject(); parseSource(source); } // // Constructor // /** * Public constructor. * @param source the source of the DataFile */ public DataFile(final String source) { if (source == null) { throw new NullPointerException("The source can not be null."); } parseSource(source); } /** * Public constructor. * @param parentFile the parent file of the DataFile * @param filename the filename of the DataFile */ public DataFile(final DataFile parentFile, final String filename) { if (parentFile == null) { throw new NullPointerException("The parent file can not be null."); } if (filename == null) { throw new NullPointerException("The name can not be null."); } final String parentSource = parentFile.getSource(); // If parent is empty, use only the filename if (parentSource == null || "".equals(parentSource)) { parseSource(filename); } else { parseSource(parentFile.getSource() + separator + filename); } } /** * Public constructor. * @param parentFile the parent file of the DataFile * @param filename the filename of the DataFile */ public DataFile(final File parentFile, final String filename) { this(new DataFile(parentFile), filename); } /** * Public constructor. * @param parentPath the parent file of the DataFile * @param filename the filename of the DataFile */ public DataFile(final Path parentPath, final String filename) { this(new DataFile(parentPath), filename); } /** * Public constructor. * @param file the source file of the DataFile */ public DataFile(final File file) { if (file == null) { throw new NullPointerException("The source file can not be null."); } parseSource(file.getPath()); } /** * Public constructor. * @param path the source path of the DataFile */ public DataFile(final Path path) { if (path == null) { throw new NullPointerException("The source path can not be null."); } parseSource(path.toFile().getPath()); } /** * Public constructor. * @param uri the URI of the DataFile */ public DataFile(final URI uri) { if (uri == null) { throw new NullPointerException("The source URI can not be null."); } parseSource(uri.toString()); } }