From 45b6e5154c06a9f6b1e08d9a01d8f7217d0c8e83 Mon Sep 17 00:00:00 2001 From: ribeaudc <ribeaudc> Date: Fri, 30 Nov 2007 07:32:31 +0000 Subject: [PATCH] [LMS-128] change: - 'tryTo*' renamed to 'try*' add: - 'ILinkMaker' interface and its implementation 'HardLinkMaker' SVN: 2863 --- .../systemsx/cisd/bds/DataStructureV1_0.java | 14 +- .../java/ch/systemsx/cisd/bds/Format.java | 6 +- .../java/ch/systemsx/cisd/bds/Utilities.java | 6 +- .../cisd/bds/handler/MappingFileHandler.java | 5 +- .../cisd/bds/hcs/IHCSFormattedData.java | 16 ++- .../cisd/bds/hcs/ImageHCSFormattedData.java | 83 ++++++++++-- .../systemsx/cisd/bds/storage/IDirectory.java | 16 ++- .../ch/systemsx/cisd/bds/storage/IFile.java | 3 + .../ch/systemsx/cisd/bds/storage/INode.java | 6 +- .../bds/storage/filesystem/Directory.java | 36 ++++- .../cisd/bds/storage/filesystem/File.java | 10 +- .../bds/storage/filesystem/HardLinkMaker.java | 123 ++++++++++++++++++ .../bds/storage/filesystem/ILinkMaker.java | 57 ++++++++ .../cisd/bds/storage/filesystem/Link.java | 6 +- .../storage/filesystem/LinkMakerProvider.java | 51 ++++++++ .../bds/storage/filesystem/NodeFactory.java | 6 +- .../bds/hcs/HCSDataStructureV1_0Test.java | 8 +- .../bds/storage/filesystem/DirectoryTest.java | 6 +- .../storage/filesystem/HardLinkMakerTest.java | 102 +++++++++++++++ 19 files changed, 503 insertions(+), 57 deletions(-) create mode 100644 bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/HardLinkMaker.java create mode 100644 bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/ILinkMaker.java create mode 100644 bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/LinkMakerProvider.java create mode 100644 bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/HardLinkMakerTest.java diff --git a/bds/source/java/ch/systemsx/cisd/bds/DataStructureV1_0.java b/bds/source/java/ch/systemsx/cisd/bds/DataStructureV1_0.java index 532c330053a..0304ac73034 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/DataStructureV1_0.java +++ b/bds/source/java/ch/systemsx/cisd/bds/DataStructureV1_0.java @@ -277,27 +277,27 @@ public class DataStructureV1_0 extends AbstractDataStructure throw new DataStructureException("Empty original data directory."); } IDirectory metaDataDirectory = getMetaDataDirectory(); - if (metaDataDirectory.tryToGetNode(Format.FORMAT_DIR) == null && format == null) + if (metaDataDirectory.tryGetNode(Format.FORMAT_DIR) == null && format == null) { throw new DataStructureException("Unspecified format."); } - if (metaDataDirectory.tryToGetNode(ExperimentIdentifier.FOLDER) == null) + if (metaDataDirectory.tryGetNode(ExperimentIdentifier.FOLDER) == null) { throw new DataStructureException("Unspecified experiment identifier."); } - if (metaDataDirectory.tryToGetNode(ExperimentRegistratorDate.FILE_NAME) == null) + if (metaDataDirectory.tryGetNode(ExperimentRegistratorDate.FILE_NAME) == null) { throw new DataStructureException("Unspecified experiment registration date."); } - if (metaDataDirectory.tryToGetNode(ExperimentRegistrator.FOLDER) == null) + if (metaDataDirectory.tryGetNode(ExperimentRegistrator.FOLDER) == null) { throw new DataStructureException("Unspecified experiment registrator."); } - if (metaDataDirectory.tryToGetNode(MeasurementEntity.FOLDER) == null) + if (metaDataDirectory.tryGetNode(MeasurementEntity.FOLDER) == null) { throw new DataStructureException("Unspecified measurement entity."); } - if (metaDataDirectory.tryToGetNode(ProcessingType.PROCESSING_TYPE) == null) + if (metaDataDirectory.tryGetNode(ProcessingType.PROCESSING_TYPE) == null) { throw new DataStructureException("Unspecified processing type."); } @@ -318,7 +318,7 @@ public class DataStructureV1_0 extends AbstractDataStructure super.performClosing(); final IDirectory metaDataDirectory = getMetaDataDirectory(); formatParameters.saveTo(getParametersDirectory()); - if (metaDataDirectory.tryToGetNode(Format.FORMAT_DIR) == null && format != null) + if (metaDataDirectory.tryGetNode(Format.FORMAT_DIR) == null && format != null) { format.saveTo(metaDataDirectory); } diff --git a/bds/source/java/ch/systemsx/cisd/bds/Format.java b/bds/source/java/ch/systemsx/cisd/bds/Format.java index 8e477ca9984..ec569b375ae 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/Format.java +++ b/bds/source/java/ch/systemsx/cisd/bds/Format.java @@ -40,13 +40,13 @@ public class Format implements IStorable */ static Format loadFrom(IDirectory directory) { - INode dir = directory.tryToGetNode(FORMAT_DIR); + INode dir = directory.tryGetNode(FORMAT_DIR); if (dir instanceof IDirectory == false) { throw new DataStructureException("Not a directory: " + dir); } IDirectory formatDir = (IDirectory) dir; - INode file = formatDir.tryToGetNode(FORMAT_CODE_FILE); + INode file = formatDir.tryGetNode(FORMAT_CODE_FILE); if (file instanceof IFile == false) { throw new DataStructureException("Not a plain file: " + file); @@ -55,7 +55,7 @@ public class Format implements IStorable String formatCode = codeFile.getStringContent().trim(); Version formatVersion = Version.loadFrom(formatDir); String variant = null; - file = formatDir.tryToGetNode(FORMAT_VARIANT_FILE); + file = formatDir.tryGetNode(FORMAT_VARIANT_FILE); if (file != null) { if (file instanceof IFile == false) diff --git a/bds/source/java/ch/systemsx/cisd/bds/Utilities.java b/bds/source/java/ch/systemsx/cisd/bds/Utilities.java index ba177890da0..012b32ad31d 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/Utilities.java +++ b/bds/source/java/ch/systemsx/cisd/bds/Utilities.java @@ -36,7 +36,7 @@ public class Utilities */ public static IDirectory getOrCreateSubDirectory(IDirectory directory, String name) { - INode node = directory.tryToGetNode(name); + INode node = directory.tryGetNode(name); if (node == null) { return directory.makeDirectory(name); @@ -58,7 +58,7 @@ public class Utilities */ public final static IDirectory getSubDirectory(final IDirectory directory, final String name) { - INode node = directory.tryToGetNode(name); + INode node = directory.tryGetNode(name); if (node == null) { throw new DataStructureException(String.format("No directory named '%s' found in directory '%s'.", name, @@ -111,7 +111,7 @@ public class Utilities { assert directory != null : String.format("Given directory can not be null."); assert name != null : String.format("Given name can not be null."); - final INode node = directory.tryToGetNode(name); + final INode node = directory.tryGetNode(name); if (node == null) { throw new DataStructureException("File '" + name + "' missing in '" + directory + "'."); diff --git a/bds/source/java/ch/systemsx/cisd/bds/handler/MappingFileHandler.java b/bds/source/java/ch/systemsx/cisd/bds/handler/MappingFileHandler.java index 405643b6445..d0222927db5 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/handler/MappingFileHandler.java +++ b/bds/source/java/ch/systemsx/cisd/bds/handler/MappingFileHandler.java @@ -44,9 +44,11 @@ public final class MappingFileHandler implements IDataStructureHandler private final IDirectory mappingDirectory; /** The root of {@link Reference#getPath()}. Usually the path to <code>standard</code> directory. */ + @SuppressWarnings("unused") private final IDirectory pathRoot; /** The root of {@link Reference#getOriginalPath()}. Usually the path to <code>original</code> directory. */ + @SuppressWarnings("unused") private final IDirectory originalPathRoot; /** @@ -119,7 +121,8 @@ public final class MappingFileHandler implements IDataStructureHandler standardOriginalMapping.put(path, new Reference(path, referenceDefinition.substring(i2 + 1), type)); } } - + + // TODO 2007-11-30, Christian Ribeaud: list all nodes present in the standard directory. private final String createMappingFile() { final StringWriter writer = new StringWriter(); diff --git a/bds/source/java/ch/systemsx/cisd/bds/hcs/IHCSFormattedData.java b/bds/source/java/ch/systemsx/cisd/bds/hcs/IHCSFormattedData.java index 8549e0f1f7e..0b408224a6a 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/hcs/IHCSFormattedData.java +++ b/bds/source/java/ch/systemsx/cisd/bds/hcs/IHCSFormattedData.java @@ -35,9 +35,17 @@ public interface IHCSFormattedData extends IFormattedData * corresponding <code>INode</code> (found in <code>data/standard</code> directory). * * @return this could be, for instance, a {@link ILink} pointing to the <code>data/original</code> directory or a - * {@link IFile} that can be extracted somewhere. Never returns <code>null</code>. - * @throws DataStructureException if a problem occurs while trying to find out the node in the whole data structure. + * {@link IFile} that can be extracted somewhere. Might return <code>null</code>. */ - public INode getNodeAt(final int channel, final Location plateLocation, final Location wellLocation) - throws DataStructureException; + public INode tryGetStandardNodeAt(final int channel, final Location plateLocation, final Location wellLocation); + + /** + * Adds a new node at given coordinates and returns it. + * + * @return the new <code>INode</code> just added. Never returns <code>null</code>. + * @param originalFileName name of the <code>original</code> directory file that is going to be added in the + * <code>standard</code> directory. + */ + public INode addStandardNode(final String originalFileName, final int channel, final Location plateLocation, + final Location wellLocation) throws DataStructureException; } diff --git a/bds/source/java/ch/systemsx/cisd/bds/hcs/ImageHCSFormattedData.java b/bds/source/java/ch/systemsx/cisd/bds/hcs/ImageHCSFormattedData.java index 32c8fb762dc..fbdbd1dfe5a 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/hcs/ImageHCSFormattedData.java +++ b/bds/source/java/ch/systemsx/cisd/bds/hcs/ImageHCSFormattedData.java @@ -29,6 +29,7 @@ import ch.systemsx.cisd.bds.IFormattedData; import ch.systemsx.cisd.bds.Utilities; import ch.systemsx.cisd.bds.storage.IDirectory; import ch.systemsx.cisd.bds.storage.INode; +import ch.systemsx.cisd.bds.storage.StorageException; /** * {@link IFormattedData} implementation for <i>HCS (High-Content Screening) with Images</i>. It is associated with @@ -58,6 +59,11 @@ public final class ImageHCSFormattedData extends AbstractFormattedData implement super(context); } + private final boolean containsOriginalData() + { + return (Boolean) getFormatParameters().getValue(ImageHCSFormat1_0.CONTAINS_ORIGINAL_DATA); + } + private final Geometry getWellGeometry() { return (Geometry) getFormatParameters().getValue(WellGeometry.WELL_GEOMETRY); @@ -108,27 +114,86 @@ public final class ImageHCSFormattedData extends AbstractFormattedData implement return ROW + wellLocation.y + "_" + COLUMN + wellLocation.x + ".tiff"; } + private final IDirectory getStandardDataDirectory() + { + return Utilities.getSubDirectory(dataDirectory, DataStructureV1_0.DIR_STANDARD); + } + + private final IDirectory getOriginalDataDirectory() + { + return Utilities.getSubDirectory(dataDirectory, DataStructureV1_0.DIR_ORIGINAL); + } + + private final static String getPlateColumnDir(final Location plateLocation) + { + return COLUMN + plateLocation.x; + } + + private final static String getPlateRowDirName(final Location plateLocation) + { + return ROW + plateLocation.y; + } + + private final static String getChannelName(final int channel) + { + return Channel.CHANNEL + channel; + } + // // IHCSFormattedData // - public final INode getNodeAt(final int channel, final Location plateLocation, final Location wellLocation) - throws DataStructureException + public final INode tryGetStandardNodeAt(final int channel, final Location plateLocation, final Location wellLocation) { checkChannel(channel); checkLocation(getPlateGeometry(), plateLocation); checkLocation(getWellGeometry(), wellLocation); - final IDirectory standardDir = Utilities.getSubDirectory(dataDirectory, DataStructureV1_0.DIR_STANDARD); - final IDirectory channelDir = Utilities.getSubDirectory(standardDir, Channel.CHANNEL + channel); - final IDirectory plateRowDir = Utilities.getSubDirectory(channelDir, ROW + plateLocation.y); - final IDirectory plateColumnDir = Utilities.getSubDirectory(plateRowDir, COLUMN + plateLocation.x); - final INode node = plateColumnDir.tryToGetNode(createWellFileName(wellLocation)); - if (node == null) + final IDirectory standardDir = getStandardDataDirectory(); + final IDirectory channelDir = Utilities.getSubDirectory(standardDir, getChannelName(channel)); + final IDirectory plateRowDir = Utilities.getSubDirectory(channelDir, getPlateRowDirName(plateLocation)); + final IDirectory plateColumnDir = Utilities.getSubDirectory(plateRowDir, getPlateColumnDir(plateLocation)); + return plateColumnDir.tryGetNode(createWellFileName(wellLocation)); + } + + public final INode addStandardNode(final String originalFileName, int channel, final Location plateLocation, + final Location wellLocation) + { + // This will check all parameters but originalFileName. + INode node = tryGetStandardNodeAt(channel, plateLocation, wellLocation); + if (node != null) { throw new DataStructureException(String.format( - "No node could be found at channel %d, plate location '%s' and well location '%s'.", channel, + "A node already exists at channel %d, plate location '%s' and well location '%s'.", channel, plateLocation, wellLocation)); } + assert originalFileName != null : "Given original file name can not be null."; + final IDirectory standardDir = getStandardDataDirectory(); + final IDirectory channelDir = Utilities.getOrCreateSubDirectory(standardDir, getChannelName(channel)); + final IDirectory plateRowDir = Utilities.getOrCreateSubDirectory(channelDir, getPlateRowDirName(plateLocation)); + final IDirectory plateColumnDir = + Utilities.getOrCreateSubDirectory(plateRowDir, getPlateColumnDir(plateLocation)); + final String wellFileName = createWellFileName(wellLocation); + final INode originalNode = getOriginalDataDirectory().tryGetNode(originalFileName); + if (originalNode == null) + { + throw new DataStructureException(String.format("No original node with name '%s' could be found.", + originalFileName)); + } + if (containsOriginalData()) + { + node = plateColumnDir.tryAddLink(wellFileName, originalNode); + } else + { + node = plateColumnDir.tryAddNode(wellFileName, originalNode); + } + if (node == null) + { + throw new StorageException( + String + .format( + "Original file name '%s' could not be added at channel %d, plate location '%s' and well location '%s'.", + originalFileName, channel, plateLocation, wellLocation)); + } return node; } diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/IDirectory.java b/bds/source/java/ch/systemsx/cisd/bds/storage/IDirectory.java index 7f0ef500185..90195b00029 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/storage/IDirectory.java +++ b/bds/source/java/ch/systemsx/cisd/bds/storage/IDirectory.java @@ -30,7 +30,7 @@ public interface IDirectory extends INode, Iterable<INode> * * @return <code>null</code> if there is no child node named <code>name</code>. */ - public INode tryToGetNode(final String name); + public INode tryGetNode(final String name); /** * Makes a new subdirectory in this directory. Does nothing if the subdirectory already exists. @@ -38,7 +38,7 @@ public interface IDirectory extends INode, Iterable<INode> * @param name Name of the new subdirectory. * @return the new subdirectory. */ - public IDirectory makeDirectory(String name); + public IDirectory makeDirectory(final String name); /** * Adds the specified real file to this directory. The content of <code>file</code> will be copied. If it is a @@ -62,6 +62,16 @@ public interface IDirectory extends INode, Iterable<INode> /** * Adds the link named <code>name</code> to this directory which refers to the specified node. + * + * @return <code>null</code> if the operation did not succeed. + */ + public ILink tryAddLink(final String name, final INode node); + + /** + * Adds given <code>node</code> to this <code>IDirectory</code> and returns a new node with given + * <code>name</code> (rename operation). + * + * @return <code>null</code> if the operation did not succeed. */ - public ILink addLink(final String name, final INode node); + public INode tryAddNode(final String name, final INode node); } diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/IFile.java b/bds/source/java/ch/systemsx/cisd/bds/storage/IFile.java index 1fce846462f..05bbdcb129e 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/storage/IFile.java +++ b/bds/source/java/ch/systemsx/cisd/bds/storage/IFile.java @@ -43,6 +43,9 @@ public interface IFile extends INode /** * Returns the content of this file node as a list of <code>String</code> objects. + * <p> + * This is useful when you know that the file content is composed of several lines. + * </p> */ public List<String> getStringContentList(); diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/INode.java b/bds/source/java/ch/systemsx/cisd/bds/storage/INode.java index 24de1be2923..6496d08bc0d 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/storage/INode.java +++ b/bds/source/java/ch/systemsx/cisd/bds/storage/INode.java @@ -16,8 +16,6 @@ package ch.systemsx.cisd.bds.storage; -import java.io.File; - /** * Abstraction of a node in a hierarchical data structure. * @@ -46,11 +44,11 @@ public interface INode * All descendants are also extracted. This is a copy operation. * </p> */ - public void extractTo(final File directory); + public void extractTo(final java.io.File directory); /** * Moves this node and all descendants to the specified directory of the file system. This node will be * automatically removed from its parent. */ - public void moveTo(final File directory); + public void moveTo(final java.io.File directory); } diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/Directory.java b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/Directory.java index c51790b7ba9..6729aadf881 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/Directory.java +++ b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/Directory.java @@ -27,7 +27,6 @@ import ch.systemsx.cisd.bds.storage.ILink; import ch.systemsx.cisd.bds.storage.INode; import ch.systemsx.cisd.bds.storage.StorageException; import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; -import ch.systemsx.cisd.common.exceptions.NotImplementedException; import ch.systemsx.cisd.common.utilities.FileUtilities; /** @@ -38,7 +37,7 @@ import ch.systemsx.cisd.common.utilities.FileUtilities; final class Directory extends AbstractNode implements IDirectory { - Directory(java.io.File directory) + Directory(final java.io.File directory) { super(directory); if (directory.isDirectory() == false) @@ -47,11 +46,17 @@ final class Directory extends AbstractNode implements IDirectory } } + private final static java.io.File getNodeFile(final INode node) + { + assert node instanceof AbstractNode : "Must be an instance of AbstractNode."; + return ((AbstractNode) node).nodeFile; + } + // // IDirectory // - public final INode tryToGetNode(final String name) + public final INode tryGetNode(final String name) { assert name != null : "Given name can not be null."; final java.io.File[] files = FileUtilities.listFiles(nodeFile); @@ -122,9 +127,19 @@ final class Directory extends AbstractNode implements IDirectory return NodeFactory.createNode(newFile); } - public final ILink addLink(final String name, final INode node) + public final ILink tryAddLink(final String name, final INode node) { - throw new NotImplementedException(); + assert node != null : "Node can not be null."; + assert name != null : "Name can not be null."; + final java.io.File file = getNodeFile(node); + final java.io.File fileLink = LinkMakerProvider.getDefaultLinkMaker().tryCreateLink(file, nodeFile, name); + if (fileLink != null) + { + final Link link = NodeFactory.createLinkNode(fileLink); + link.setParent(this); + return link; + } + return null; } public final Iterator<INode> iterator() @@ -170,11 +185,18 @@ final class Directory extends AbstractNode implements IDirectory } } + public final INode tryAddNode(final String name, final INode node) + { + assert node != null : "Node can not be null"; + assert name != null : "Name can not be null."; + final java.io.File file = getNodeFile(node); + return addFile(file, true); + } + public final void removeNode(final INode node) { assert node != null : "Node could not be null"; - AbstractNode abstractNode = (AbstractNode) node; - final java.io.File file = abstractNode.nodeFile; + final java.io.File file = getNodeFile(node); if (file.isDirectory()) { if (FileUtilities.deleteRecursively(file, null) == false) diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/File.java b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/File.java index 559e0c5c98b..791d86e8828 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/File.java +++ b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/File.java @@ -34,15 +34,19 @@ import ch.systemsx.cisd.common.utilities.FileUtilities; * * @author Franz-Josef Elmer */ -class File extends AbstractNode implements IFile +final class File extends AbstractNode implements IFile { - File(java.io.File file) + File(final java.io.File file) { super(file); assert file.isFile() : "Not a file " + file.getAbsolutePath(); } - public byte[] getBinaryContent() + // + // IFile + // + + public final byte[] getBinaryContent() { InputStream inputStream = getInputStream(); try diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/HardLinkMaker.java b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/HardLinkMaker.java new file mode 100644 index 00000000000..a3e52766485 --- /dev/null +++ b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/HardLinkMaker.java @@ -0,0 +1,123 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.bds.storage.filesystem; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.logging.BufferedAppender; +import ch.systemsx.cisd.common.utilities.OSUtilities; +import ch.systemsx.cisd.common.utilities.ProcessExecutionHelper; + +/** + * An <code>ILinkMaker</code> implementation which is able to make hard links using the operating system. + * <p> + * Note that the current implementation only works for files. + * </p> + * + * @author Christian Ribeaud + */ +public final class HardLinkMaker implements ILinkMaker +{ + static final String ALREADY_EXISTS_FORMAT = "Given file '%s' already exists."; + + private static final String HARD_LINK_EXEC = "ln"; + + private static HardLinkMaker instance; + + private final String linkExecPath; + + private final BufferedAppender loggerRecorder; + + private HardLinkMaker(final String linkExecPath) + { + this.linkExecPath = linkExecPath; + loggerRecorder = new BufferedAppender("%m", Level.ERROR); + } + + private final List<String> createLnCmdLine(final File srcFile, final File destFile) + { + List<String> tokens = new ArrayList<String>(); + tokens.add(linkExecPath); + tokens.add(srcFile.getAbsolutePath()); + // The destination file does not yet exist. Is going to be created and is the link. + tokens.add(destFile.getAbsolutePath()); + return tokens; + } + + /** + * Returns the unique instance of this class. + */ + static synchronized final HardLinkMaker getInstance() throws EnvironmentFailureException + { + if (instance == null) + { + final File lnExec = OSUtilities.findExecutable(HARD_LINK_EXEC); + if (lnExec == null) + { + throw EnvironmentFailureException.fromTemplate( + "No hard link executable '%s' could be found in path '%s'.", HARD_LINK_EXEC, OSUtilities + .getSafeOSPath()); + } + instance = new HardLinkMaker(lnExec.getAbsolutePath()); + } + return instance; + } + + // + // ILinkMaker + // + + public final File tryCreateLink(final java.io.File file, final java.io.File destDir, final String nameOrNull) + throws EnvironmentFailureException + { + assert file != null && file.isFile() : "Given file can not be null and must be a file."; + assert destDir != null && destDir.isDirectory() : "Given destination directory can not be null and must be a directory."; + final String destName; + if (nameOrNull == null) + { + destName = file.getName(); + } else + { + destName = nameOrNull; + } + final File destFile = new File(destDir, destName); + if (destFile.exists()) + { + throw new IllegalArgumentException(String.format(ALREADY_EXISTS_FORMAT, destFile.getAbsolutePath())); + } + final List<String> cmd = createLnCmdLine(file, destFile); + final Logger rootLogger = Logger.getRootLogger(); + loggerRecorder.resetLogContent(); + final boolean ok = ProcessExecutionHelper.runAndLog(cmd, rootLogger, rootLogger); + if (ok == false) + { + String message = loggerRecorder.getLogContent(); + if (message.length() == 0) + { + return null; + } + throw new EnvironmentFailureException(message); + } + return destFile; + } +} diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/ILinkMaker.java b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/ILinkMaker.java new file mode 100644 index 00000000000..81d1e80d612 --- /dev/null +++ b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/ILinkMaker.java @@ -0,0 +1,57 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.bds.storage.filesystem; + +import java.io.File; + +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; + +/** + * Kind of strategy how to 'serialize' a <code>ILink</code> implementation. + * + * @author Christian Ribeaud + */ +public interface ILinkMaker +{ + + /** + * An default <code>ILinkMaker</code> implementation which always returns <code>null</code> without trying to do + * anything. + */ + public final static ILinkMaker DEFAULT = new ILinkMaker() + { + + // + // ILinkMaker + // + + public final File tryCreateLink(final File file, final File destDir, final String nameOrNull) + throws EnvironmentFailureException + { + return null; + } + }; + + /** + * Tries to create a link to given <var>file</var> in given <var>destDir</var>. + * + * @param nameOrNull the link name in the destination directory. + * @return might returns <code>null</code> if the application was not able to create the link. + */ + public File tryCreateLink(final File file, final File destDir, final String nameOrNull) + throws EnvironmentFailureException; +} diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/Link.java b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/Link.java index 604de64e9b8..5d11cc79273 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/Link.java +++ b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/Link.java @@ -19,6 +19,7 @@ package ch.systemsx.cisd.bds.storage.filesystem; import java.io.File; import ch.systemsx.cisd.bds.storage.IDirectory; +import ch.systemsx.cisd.bds.storage.IFile; import ch.systemsx.cisd.bds.storage.ILink; import ch.systemsx.cisd.bds.storage.INode; @@ -29,7 +30,6 @@ import ch.systemsx.cisd.bds.storage.INode; */ final class Link implements ILink { - private final String name; private final INode reference; @@ -40,7 +40,7 @@ final class Link implements ILink { assert name != null : "A name must be specified."; assert reference != null : "Reference can not be null."; - assert reference instanceof ILink : "Link of link not supported."; + assert reference instanceof IFile && reference.isValid() : "Given reference must be a valid IFile implementation"; this.name = name; this.reference = reference; } @@ -88,7 +88,7 @@ final class Link implements ILink } if (parent != null) { - final INode node = parent.tryToGetNode(name); + final INode node = parent.tryGetNode(name); return node != null && node.isValid(); } return true; diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/LinkMakerProvider.java b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/LinkMakerProvider.java new file mode 100644 index 00000000000..290b3f9d437 --- /dev/null +++ b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/LinkMakerProvider.java @@ -0,0 +1,51 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.bds.storage.filesystem; + +import ch.systemsx.cisd.common.utilities.OSUtilities; + +/** + * A provider of {@link ILinkMaker} implementations. + * + * @author Christian Ribeaud + */ +public final class LinkMakerProvider +{ + + private LinkMakerProvider() + { + // This class can not be instantiated. + } + + /** + * Returns a default <code>ILinkMaker</code> implementation. + * <p> + * Never returns <code>null</code> but could return the default implementation. + * </p> + * + * @see ILinkMaker#DEFAULT + */ + public final static ILinkMaker getDefaultLinkMaker() + { + if (OSUtilities.isWindows()) + { + return ILinkMaker.DEFAULT; + } + return HardLinkMaker.getInstance(); + } + +} diff --git a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/NodeFactory.java b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/NodeFactory.java index 9e394e94777..b647b9e5ad1 100644 --- a/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/NodeFactory.java +++ b/bds/source/java/ch/systemsx/cisd/bds/storage/filesystem/NodeFactory.java @@ -67,12 +67,12 @@ public final class NodeFactory } } - private static Link createLinkNode(final java.io.File file) throws EnvironmentFailureException + public final static Link createLinkNode(final java.io.File file) throws EnvironmentFailureException { final String absolutePath = file.getAbsolutePath(); final String canonicalPath = getCanonicalPath(file); - assert absolutePath.equals(canonicalPath) == false : "Given file must be a link"; - return new Link(file.getName(), createNode(new java.io.File(getCanonicalPath(file)))); + assert absolutePath.equals(canonicalPath) == false : "Given file must be a link."; + return new Link(file.getName(), createNode(new java.io.File(canonicalPath))); } /** diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/HCSDataStructureV1_0Test.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/HCSDataStructureV1_0Test.java index a4b19b867d6..7608680e656 100644 --- a/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/HCSDataStructureV1_0Test.java +++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/HCSDataStructureV1_0Test.java @@ -126,7 +126,7 @@ public final class HCSDataStructureV1_0Test extends AbstractFileSystemTestCase final IHCSFormattedData formattedData = (IHCSFormattedData) dataStructure.getFormattedData(); try { - formattedData.getNodeAt(3, new Location(1, 1), new Location(1, 1)); + formattedData.tryGetStandardNodeAt(3, new Location(1, 1), new Location(1, 1)); fail("3 > 2"); } catch (IndexOutOfBoundsException ex) { @@ -134,7 +134,7 @@ public final class HCSDataStructureV1_0Test extends AbstractFileSystemTestCase } try { - formattedData.getNodeAt(2, new Location(1, 1), new Location(1, 1)); + formattedData.tryGetStandardNodeAt(2, new Location(1, 1), new Location(1, 1)); fail("No directory named 'channel2' found."); } catch (DataStructureException ex) { @@ -142,13 +142,13 @@ public final class HCSDataStructureV1_0Test extends AbstractFileSystemTestCase } try { - formattedData.getNodeAt(1, new Location(1, 3), new Location(1, 1)); + formattedData.tryGetStandardNodeAt(1, new Location(1, 3), new Location(1, 1)); fail("Given geometry '2x3' does not contain location '[x=1,y=3]'."); } catch (IllegalArgumentException ex) { assertTrue(ex.getMessage().indexOf("does not contain location") > -1); } - final INode node = formattedData.getNodeAt(1, new Location(1, 1), new Location(1, 1)); + final INode node = formattedData.tryGetStandardNodeAt(1, new Location(1, 1), new Location(1, 1)); assertEquals("row1_column1.tiff", node.getName()); dataStructure.close(); } diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/DirectoryTest.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/DirectoryTest.java index 597de4c2dfd..d06c00f24d2 100644 --- a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/DirectoryTest.java +++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/DirectoryTest.java @@ -59,16 +59,16 @@ public final class DirectoryTest extends AbstractFileSystemTestCase File copiedRealDir = new File(dest, "dir"); assertTrue(copiedRealDir.isDirectory()); IDirectory cd = (IDirectory) copiedDir; - INode node = cd.tryToGetNode("p1"); + INode node = cd.tryGetNode("p1"); assertNotNull(node); assertTrue(node instanceof IFile); assertEquals("property 1\n", ((IFile) node).getStringContent()); assertEquals("property 1\n", FileUtilities.loadToString(new File(copiedRealDir, "p1"))); - node = cd.tryToGetNode("subdir"); + node = cd.tryGetNode("subdir"); assertEquals("subdir", node.getName()); assertNotNull(node); assertTrue(node instanceof IDirectory); - node = ((IDirectory) node).tryToGetNode("p2"); + node = ((IDirectory) node).tryGetNode("p2"); File copiedRealSubDir = new File(copiedRealDir, "subdir"); assertTrue(copiedRealSubDir.isDirectory()); assertEquals("p2", node.getName()); diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/HardLinkMakerTest.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/HardLinkMakerTest.java new file mode 100644 index 00000000000..ed77511f4e3 --- /dev/null +++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/HardLinkMakerTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.bds.storage.filesystem; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertTrue; +import static org.testng.AssertJUnit.fail; + +import java.io.File; +import java.io.IOException; + +import org.apache.commons.io.FileUtils; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.common.utilities.AbstractFileSystemTestCase; + +/** + * Test cases for corresponding {@link HardLinkMaker} class. + * + * @author Christian Ribeaud + */ +@Test(groups = + { "requires_unix" }) +public final class HardLinkMakerTest extends AbstractFileSystemTestCase +{ + + private static final String FILE_CONTENT = "This is my content"; + + private static final String SOURCE_FILE_NAME = "source.txt"; + + private File createFile() throws IOException + { + final File srcFile = new File(new File(workingDirectory, "dir"), SOURCE_FILE_NAME); + assertFalse(srcFile.exists()); + FileUtils.writeStringToFile(srcFile, FILE_CONTENT); + assertTrue(srcFile.exists()); + return srcFile; + } + + @Test + public final void testCreateLinkParameters() throws IOException + { + try + { + HardLinkMaker.getInstance().tryCreateLink(null, workingDirectory, null); + fail("Null value not accepted."); + } catch (AssertionError ex) + { + // Nothing to do here. + } + try + { + HardLinkMaker.getInstance().tryCreateLink(workingDirectory, workingDirectory, null); + fail("First parameter must be an existing file."); + } catch (AssertionError ex) + { + // Nothing to do here. + } + final File srcFile = createFile(); + final File destinationDir = new File(workingDirectory, "dir"); + try + { + HardLinkMaker.getInstance().tryCreateLink(srcFile, destinationDir, null); + fail("File already exits."); + } catch (final IllegalArgumentException ex) + { + assertEquals(String.format(HardLinkMaker.ALREADY_EXISTS_FORMAT, new File(destinationDir, srcFile.getName()) + .getAbsolutePath()), ex.getMessage()); + } + + } + + @Test + public final void testCreateLink() throws IOException + { + final File srcFile = createFile(); + HardLinkMaker.getInstance().tryCreateLink(srcFile, workingDirectory, null); + final File destFile = new File(workingDirectory, SOURCE_FILE_NAME); + assertTrue(destFile.exists()); + assertEquals(FILE_CONTENT, FileUtils.readFileToString(destFile)); + assertTrue(srcFile.delete()); + assertFalse(srcFile.exists()); + // We removed the source file but the destination file (the hard link) still persists. + assertTrue(destFile.exists()); + assertEquals(FILE_CONTENT, FileUtils.readFileToString(destFile)); + } +} \ No newline at end of file -- GitLab