From eae3c0a15129388f468f805899614c8fc0a09445 Mon Sep 17 00:00:00 2001
From: ribeaudc <ribeaudc>
Date: Mon, 5 Nov 2007 12:00:04 +0000
Subject: [PATCH] [LMS-107] add: - HCS image format to BDS library

SVN: 2376
---
 .../systemsx/cisd/bds/DataStructureV1_0.java  | 11 ++-
 .../ch/systemsx/cisd/bds/FormatParameter.java |  6 +-
 .../systemsx/cisd/bds/FormatParameters.java   | 52 ++++++++---
 .../cisd/bds/IFormatParameterFactory.java     | 56 ++++++++++++
 .../systemsx/cisd/bds/IFormatParameters.java  |  2 +-
 .../systemsx/cisd/bds/UnknownFormat1_0.java   |  3 +-
 .../java/ch/systemsx/cisd/bds/Utilities.java  |  4 +-
 .../systemsx/cisd/bds/{ => hcs}/Channel.java  |  5 +-
 .../cisd/bds/{ => hcs}/ChannelList.java       |  4 +-
 .../cisd/bds/hcs/FormatParameterFactory.java  | 61 +++++++++++++
 .../systemsx/cisd/bds/{ => hcs}/Geometry.java |  7 +-
 .../cisd/bds/hcs/ImageHCSFormat1_0.java       | 50 +++++++++++
 .../cisd/bds/{ => hcs}/PlateGeometry.java     |  2 +-
 .../cisd/bds/{ => hcs}/WellGeometry.java      |  2 +-
 .../bds/storage/filesystem/NodeFactory.java   | 59 +++++++++---
 .../cisd/bds/ChecksumBuilderTest.java         | 40 +++------
 .../ch/systemsx/cisd/bds/UtilitiesTest.java   | 30 ++-----
 .../ChannelListTest.java}                     | 24 ++---
 .../cisd/bds/hcs/PlateGeometryTest.java       | 89 ++++++++++++++++++
 .../cisd/bds/hcs/WellGeometryTest.java        | 90 +++++++++++++++++++
 .../bds/storage/filesystem/DirectoryTest.java | 43 ++++-----
 .../storage/filesystem/FileStorageTest.java   | 17 ++--
 .../cisd/bds/storage/filesystem/FileTest.java | 30 ++++---
 .../storage/filesystem/NodeFactoryTest.java   |  9 +-
 24 files changed, 543 insertions(+), 153 deletions(-)
 create mode 100644 bds/source/java/ch/systemsx/cisd/bds/IFormatParameterFactory.java
 rename bds/source/java/ch/systemsx/cisd/bds/{ => hcs}/Channel.java (94%)
 rename bds/source/java/ch/systemsx/cisd/bds/{ => hcs}/ChannelList.java (94%)
 create mode 100644 bds/source/java/ch/systemsx/cisd/bds/hcs/FormatParameterFactory.java
 rename bds/source/java/ch/systemsx/cisd/bds/{ => hcs}/Geometry.java (90%)
 create mode 100644 bds/source/java/ch/systemsx/cisd/bds/hcs/ImageHCSFormat1_0.java
 rename bds/source/java/ch/systemsx/cisd/bds/{ => hcs}/PlateGeometry.java (97%)
 rename bds/source/java/ch/systemsx/cisd/bds/{ => hcs}/WellGeometry.java (97%)
 rename bds/sourceTest/java/ch/systemsx/cisd/bds/{storage/filesystem/StorageTestCase.java => hcs/ChannelListTest.java} (53%)
 create mode 100644 bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/PlateGeometryTest.java
 create mode 100644 bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/WellGeometryTest.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 1447c1b2330..71899de46da 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/DataStructureV1_0.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/DataStructureV1_0.java
@@ -64,7 +64,7 @@ public class DataStructureV1_0 extends AbstractDataStructure
     /**
      * Creates a new instance relying on the specified storage.
      */
-    public DataStructureV1_0(IStorage storage)
+    public DataStructureV1_0(final IStorage storage)
     {
         super(storage);
     }
@@ -257,6 +257,15 @@ public class DataStructureV1_0 extends AbstractDataStructure
         standardOriginalMapping.put(path, reference);
     }
 
+    /**
+     * Sets a different <code>IFormatParameterFactory</code> implementation than the default one to encapsulated
+     * <code>FormatParameters</code>.
+     */
+    public void setFormatParametersFactory(final IFormatParameterFactory formatParameterFactory)
+    {
+        formatParameters.setFormatParameterFactory(formatParameterFactory);
+    }
+
     @Override
     protected void assertValid()
     {
diff --git a/bds/source/java/ch/systemsx/cisd/bds/FormatParameter.java b/bds/source/java/ch/systemsx/cisd/bds/FormatParameter.java
index 1f6f40d729a..de2d2315adb 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/FormatParameter.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/FormatParameter.java
@@ -25,7 +25,7 @@ public final class FormatParameter
 {
     private final String name;
 
-    private final String value;
+    private final Object value;
 
     /**
      * Creates an instance for the specified name and value.
@@ -33,7 +33,7 @@ public final class FormatParameter
      * @param name A non-empty string as the name of the parameter.
      * @param value A non-<code>null</code> string as the value.
      */
-    public FormatParameter(String name, String value)
+    public FormatParameter(final String name, final Object value)
     {
         assert name != null && name.length() > 0 : "Unspecified parameter name.";
         this.name = name;
@@ -52,7 +52,7 @@ public final class FormatParameter
     /**
      * Returns the value of this parameter.
      */
-    public final String getValue()
+    public final Object getValue()
     {
         return value;
     }
diff --git a/bds/source/java/ch/systemsx/cisd/bds/FormatParameters.java b/bds/source/java/ch/systemsx/cisd/bds/FormatParameters.java
index 317734aa8de..4d4eefc7bf5 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/FormatParameters.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/FormatParameters.java
@@ -21,7 +21,6 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 
 import ch.systemsx.cisd.bds.storage.IDirectory;
-import ch.systemsx.cisd.bds.storage.IFile;
 import ch.systemsx.cisd.bds.storage.INode;
 
 /**
@@ -29,19 +28,27 @@ import ch.systemsx.cisd.bds.storage.INode;
  * 
  * @author Franz-Josef Elmer
  */
-class FormatParameters implements IFormatParameters, IStorable
+final class FormatParameters implements IFormatParameters, IStorable
 {
     private final Map<String, FormatParameter> parameters = new LinkedHashMap<String, FormatParameter>();
 
-    void loadFrom(IDirectory directory)
+    /**
+     * The <code>IFormatParameterFactory</code> implementation used here.
+     * <p>
+     * Initialized with the default implementation {@link IFormatParameterFactory#DEFAULT_FORMAT_PARAMETER_FACTORY}.
+     * </p>
+     */
+    private IFormatParameterFactory formatParameterFactory = IFormatParameterFactory.DEFAULT_FORMAT_PARAMETER_FACTORY;
+
+    final void loadFrom(final IDirectory directory)
     {
         parameters.clear();
         for (INode node : directory)
         {
-            if (node instanceof IFile)
+            final FormatParameter formatParameter = formatParameterFactory.createFormatParameter(node);
+            if (formatParameter != null)
             {
-                IFile file = (IFile) node;
-                addParameter(new FormatParameter(file.getName(), file.getStringContent().trim()));
+                addParameter(formatParameter);
             }
         }
     }
@@ -52,18 +59,36 @@ class FormatParameters implements IFormatParameters, IStorable
 
     public final void saveTo(final IDirectory directory)
     {
-        for (FormatParameter parameter : parameters.values())
+        for (final FormatParameter parameter : parameters.values())
         {
-            directory.addKeyValuePair(parameter.getName(), parameter.getValue());
+            final Object value = parameter.getValue();
+            assert value != null : "Parameter value can not be null.";
+            if (value instanceof String)
+            {
+                directory.addKeyValuePair(parameter.getName(), (String) value);
+            } else if (value instanceof IStorable)
+            {
+                ((IStorable) value).saveTo(directory);
+            } else
+            {
+                throw new IllegalArgumentException(String.format(
+                        "Parameter value '%s' must be a String or an IStorable implementation.", value));
+            }
         }
     }
 
+    /** Sets a different <code>IFormatParameterFactory</code> implementation than the default one. */
+    final void setFormatParameterFactory(final IFormatParameterFactory formatParameterFactory)
+    {
+        this.formatParameterFactory = formatParameterFactory;
+    }
+
     /**
      * Adds the specified parameter.
      * 
      * @throws IllegalArgumentException if they is already a parameter with same name as <code>parameter</code>.
      */
-    void addParameter(FormatParameter parameter)
+    final void addParameter(final FormatParameter parameter)
     {
         String name = parameter.getName();
         if (parameters.containsKey(name))
@@ -73,7 +98,11 @@ class FormatParameters implements IFormatParameters, IStorable
         parameters.put(name, parameter);
     }
 
-    public String getValue(String parameterName)
+    //
+    // IFormatParameters
+    //
+
+    public final Object getValue(final String parameterName)
     {
         FormatParameter formatParameter = parameters.get(parameterName);
         if (formatParameter == null)
@@ -83,9 +112,8 @@ class FormatParameters implements IFormatParameters, IStorable
         return formatParameter.getValue();
     }
 
-    public Iterator<FormatParameter> iterator()
+    public final Iterator<FormatParameter> iterator()
     {
         return parameters.values().iterator();
     }
-
 }
diff --git a/bds/source/java/ch/systemsx/cisd/bds/IFormatParameterFactory.java b/bds/source/java/ch/systemsx/cisd/bds/IFormatParameterFactory.java
new file mode 100644
index 00000000000..fd291b6e5d8
--- /dev/null
+++ b/bds/source/java/ch/systemsx/cisd/bds/IFormatParameterFactory.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import ch.systemsx.cisd.bds.storage.IFile;
+import ch.systemsx.cisd.bds.storage.INode;
+
+/**
+ * Interface to be implemented if you want to set a different <code>IFormatParameterFactory</code> in
+ * {@link FormatParameters}.
+ * 
+ * @author Christian Ribeaud
+ */
+public interface IFormatParameterFactory
+{
+
+    public final static IFormatParameterFactory DEFAULT_FORMAT_PARAMETER_FACTORY = new IFormatParameterFactory()
+        {
+
+            //
+            // IFormatParameterFactory
+            //
+
+            public final FormatParameter createFormatParameter(final INode node)
+            {
+                if (node instanceof IFile)
+                {
+                    final IFile file = (IFile) node;
+                    return new FormatParameter(file.getName(), file.getStringContent().trim());
+                }
+                return null;
+            }
+        };
+
+    /**
+     * Creates a <code>FormatParameter</code> from given <var>node</var>.
+     * 
+     * @return <code>null</code> if no appropriate <code>FormatParameter</code> could be created from given
+     *         <code>INode</code>.
+     */
+    public FormatParameter createFormatParameter(final INode node);
+}
diff --git a/bds/source/java/ch/systemsx/cisd/bds/IFormatParameters.java b/bds/source/java/ch/systemsx/cisd/bds/IFormatParameters.java
index 50b36d2701a..398b81d8322 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/IFormatParameters.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/IFormatParameters.java
@@ -28,5 +28,5 @@ public interface IFormatParameters extends Iterable<FormatParameter>
      * 
      * @throws IllegalArgumentException if there is no parameter named as specified.
      */
-    public String getValue(String parameterName);
+    public Object getValue(final String parameterName);
 }
diff --git a/bds/source/java/ch/systemsx/cisd/bds/UnknownFormat1_0.java b/bds/source/java/ch/systemsx/cisd/bds/UnknownFormat1_0.java
index cec05c6e51c..d4610298bb6 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/UnknownFormat1_0.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/UnknownFormat1_0.java
@@ -23,6 +23,7 @@ package ch.systemsx.cisd.bds;
  */
 public final class UnknownFormat1_0 extends Format
 {
+    private static final String FORMAT_CODE = "UNKNOWN";
     /**
      * The one and only one instance.
      */
@@ -30,7 +31,7 @@ public final class UnknownFormat1_0 extends Format
 
     private UnknownFormat1_0()
     {
-        super("UNKNOWN", new Version(1, 0), null);
+        super(FORMAT_CODE, new Version(1, 0), null);
     }
 
     
diff --git a/bds/source/java/ch/systemsx/cisd/bds/Utilities.java b/bds/source/java/ch/systemsx/cisd/bds/Utilities.java
index a4943f6174f..89eabf7e6c6 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/Utilities.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/Utilities.java
@@ -107,13 +107,13 @@ public class Utilities
     public final static int getNumber(final IDirectory directory, final String name)
     {
         // No assertion here as 'getString(IDirectory, String)' already does it.
-        String value = getTrimmedString(directory, name);
+        final String value = getTrimmedString(directory, name);
         try
         {
             return Integer.parseInt(value);
         } catch (NumberFormatException ex)
         {
-            throw new DataStructureException("Value of " + name + " version file is not a number: " + value);
+            throw new DataStructureException("Value of '" + name + "' version file is not a number: " + value);
         }
     }
 }
diff --git a/bds/source/java/ch/systemsx/cisd/bds/Channel.java b/bds/source/java/ch/systemsx/cisd/bds/hcs/Channel.java
similarity index 94%
rename from bds/source/java/ch/systemsx/cisd/bds/Channel.java
rename to bds/source/java/ch/systemsx/cisd/bds/hcs/Channel.java
index 72b6d170c55..0620cdc5c1f 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/Channel.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/hcs/Channel.java
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.bds;
+package ch.systemsx.cisd.bds.hcs;
 
+import ch.systemsx.cisd.bds.DataStructureException;
+import ch.systemsx.cisd.bds.IStorable;
+import ch.systemsx.cisd.bds.Utilities;
 import ch.systemsx.cisd.bds.storage.IDirectory;
 
 /**
diff --git a/bds/source/java/ch/systemsx/cisd/bds/ChannelList.java b/bds/source/java/ch/systemsx/cisd/bds/hcs/ChannelList.java
similarity index 94%
rename from bds/source/java/ch/systemsx/cisd/bds/ChannelList.java
rename to bds/source/java/ch/systemsx/cisd/bds/hcs/ChannelList.java
index e13741a1d9f..a724bb74054 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/ChannelList.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/hcs/ChannelList.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.bds;
+package ch.systemsx.cisd.bds.hcs;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import ch.systemsx.cisd.bds.DataStructureException;
+import ch.systemsx.cisd.bds.IStorable;
 import ch.systemsx.cisd.bds.storage.IDirectory;
 import ch.systemsx.cisd.bds.storage.INode;
 
diff --git a/bds/source/java/ch/systemsx/cisd/bds/hcs/FormatParameterFactory.java b/bds/source/java/ch/systemsx/cisd/bds/hcs/FormatParameterFactory.java
new file mode 100644
index 00000000000..68a780cb445
--- /dev/null
+++ b/bds/source/java/ch/systemsx/cisd/bds/hcs/FormatParameterFactory.java
@@ -0,0 +1,61 @@
+/*
+ * 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.hcs;
+
+import ch.systemsx.cisd.bds.FormatParameter;
+import ch.systemsx.cisd.bds.IFormatParameterFactory;
+import ch.systemsx.cisd.bds.storage.IDirectory;
+import ch.systemsx.cisd.bds.storage.INode;
+
+/**
+ * A customized <code>IFormatParameterFactory</code> implementation suitable for <i>HCS (High-Content Screening) with
+ * Images</i>.
+ * 
+ * @author Christian Ribeaud
+ */
+public final class FormatParameterFactory implements IFormatParameterFactory
+{
+
+    //
+    // IFormatParameterFactory
+    //
+
+    public final FormatParameter createFormatParameter(final INode node)
+    {
+        assert node != null : "Given node can not be null.";
+        final String nodeName = node.getName();
+        if (node instanceof IDirectory)
+        {
+            final IDirectory directory = (IDirectory) node;
+            if (nodeName.equals(PlateGeometry.PLATE_GEOMETRY))
+            {
+                return new FormatParameter(nodeName, PlateGeometry.loadFrom(directory));
+            } else if (nodeName.equals(WellGeometry.WELL_GEOMETRY))
+            {
+                return new FormatParameter(nodeName, WellGeometry.loadFrom(directory));
+            } else if (nodeName.equals(ChannelList.NUMBER_OF_CHANNELS))
+            {
+                return new FormatParameter(nodeName, ChannelList.loadFrom(directory));
+            } else
+            {
+                // Probably 'channelN'. As already loaded, returns null here.
+                return null;
+            }
+        }
+        return IFormatParameterFactory.DEFAULT_FORMAT_PARAMETER_FACTORY.createFormatParameter(node);
+    }
+}
diff --git a/bds/source/java/ch/systemsx/cisd/bds/Geometry.java b/bds/source/java/ch/systemsx/cisd/bds/hcs/Geometry.java
similarity index 90%
rename from bds/source/java/ch/systemsx/cisd/bds/Geometry.java
rename to bds/source/java/ch/systemsx/cisd/bds/hcs/Geometry.java
index abafed7aa27..d4e13d270ba 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/Geometry.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/hcs/Geometry.java
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.bds;
+package ch.systemsx.cisd.bds.hcs;
 
+import ch.systemsx.cisd.bds.IStorable;
+import ch.systemsx.cisd.bds.Utilities;
 import ch.systemsx.cisd.bds.storage.IDirectory;
 
 /**
@@ -73,6 +75,8 @@ public class Geometry implements IStorable
      */
     final static Geometry loadFrom(final IDirectory directory, final String geometryDirectoryName)
     {
+        assert directory != null : "Given directory can not be null.";
+        assert geometryDirectoryName != null && geometryDirectoryName.trim().length() > 0 : "Given subdirectory name can not be empty.";
         final IDirectory geometryFolder = Utilities.getSubDirectory(directory, geometryDirectoryName);
         return new Geometry(Utilities.getNumber(geometryFolder, ROWS), Utilities.getNumber(geometryFolder, COLUMNS));
     }
@@ -83,6 +87,7 @@ public class Geometry implements IStorable
 
     public final void saveTo(final IDirectory directory)
     {
+        assert directory != null : "Given directory can not be null.";
         final IDirectory geometryDirectory = directory.makeDirectory(getGeometryDirectoryName());
         geometryDirectory.addKeyValuePair(ROWS, toString(getRows()));
         geometryDirectory.addKeyValuePair(COLUMNS, toString(getColumns()));
diff --git a/bds/source/java/ch/systemsx/cisd/bds/hcs/ImageHCSFormat1_0.java b/bds/source/java/ch/systemsx/cisd/bds/hcs/ImageHCSFormat1_0.java
new file mode 100644
index 00000000000..6ad2a41c4ec
--- /dev/null
+++ b/bds/source/java/ch/systemsx/cisd/bds/hcs/ImageHCSFormat1_0.java
@@ -0,0 +1,50 @@
+/*
+ * 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.hcs;
+
+import ch.systemsx.cisd.bds.Format;
+import ch.systemsx.cisd.bds.Version;
+
+/**
+ * <code>Format</code> extension for <i>HCS (High-Content Screening) with Images</i>.
+ * 
+ * @author Christian Ribeaud
+ */
+public final class ImageHCSFormat1_0 extends Format
+{
+
+    private static final String FORMAT_CODE = "IMAGE_HCS";
+
+    /** Key for setting the measurement device that was used to take the data of this data set. */
+    public final static String DEVICE_ID = "device_id";
+
+    /**
+     * Flag ({@link Boolean#TRUE} or {@link Boolean#FALSE}) specifying whether the data directory contains the
+     * original data or not.
+     */
+    public final static String CONTAINS_ORIGINAL_DATA = "contains_original_data";
+
+    /**
+     * The one and only one instance.
+     */
+    public static final Format UNKNOWN_1_0 = new ImageHCSFormat1_0();
+
+    private ImageHCSFormat1_0()
+    {
+        super(FORMAT_CODE, new Version(1, 0), null);
+    }
+}
diff --git a/bds/source/java/ch/systemsx/cisd/bds/PlateGeometry.java b/bds/source/java/ch/systemsx/cisd/bds/hcs/PlateGeometry.java
similarity index 97%
rename from bds/source/java/ch/systemsx/cisd/bds/PlateGeometry.java
rename to bds/source/java/ch/systemsx/cisd/bds/hcs/PlateGeometry.java
index bbb35951e68..c1d862af86b 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/PlateGeometry.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/hcs/PlateGeometry.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.bds;
+package ch.systemsx.cisd.bds.hcs;
 
 import ch.systemsx.cisd.bds.storage.IDirectory;
 
diff --git a/bds/source/java/ch/systemsx/cisd/bds/WellGeometry.java b/bds/source/java/ch/systemsx/cisd/bds/hcs/WellGeometry.java
similarity index 97%
rename from bds/source/java/ch/systemsx/cisd/bds/WellGeometry.java
rename to bds/source/java/ch/systemsx/cisd/bds/hcs/WellGeometry.java
index 893e9b6ef06..5dc44524530 100644
--- a/bds/source/java/ch/systemsx/cisd/bds/WellGeometry.java
+++ b/bds/source/java/ch/systemsx/cisd/bds/hcs/WellGeometry.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.bds;
+package ch.systemsx.cisd.bds.hcs;
 
 import ch.systemsx.cisd.bds.storage.IDirectory;
 
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 f851a4c3d93..9e394e94777 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
@@ -18,6 +18,8 @@ package ch.systemsx.cisd.bds.storage.filesystem;
 
 import java.io.IOException;
 
+import ch.systemsx.cisd.bds.storage.IDirectory;
+import ch.systemsx.cisd.bds.storage.IFile;
 import ch.systemsx.cisd.bds.storage.INode;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 
@@ -39,25 +41,58 @@ public final class NodeFactory
     public static INode createNode(final java.io.File file) throws EnvironmentFailureException
     {
         assert file != null : "Unspecified node";
-        String absolutePath = file.getAbsolutePath();
+        final String absolutePath = file.getAbsolutePath();
+        final String canonicalPath = getCanonicalPath(file);
+        if (absolutePath.equals(canonicalPath) == false)
+        {
+            return createLinkNode(file);
+        }
+        if (file.isDirectory())
+        {
+            return createDirectoryNode(file);
+        }
+        return createFileNode(file);
+    }
+
+    private final static String getCanonicalPath(final java.io.File file)
+    {
+        assert file != null : "Unspecified node";
         try
         {
-            String canonicalPath = file.getCanonicalPath();
-            if (absolutePath.equals(canonicalPath) == false)
-            {
-                return new Link(file.getName(), createNode(new java.io.File(canonicalPath)));
-            }
-            if (file.isDirectory())
-            {
-                return new Directory(file);
-            }
-            return new File(file);
+            return file.getCanonicalPath();
         } catch (IOException ex)
         {
-            throw new EnvironmentFailureException("Couldn't get canonical path of file " + absolutePath, ex);
+            throw new EnvironmentFailureException("Couldn't get canonical path of file '" + file.getAbsolutePath()
+                    + "'", ex);
         }
     }
 
+    private 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))));
+    }
+
+    /**
+     * Creates a <code>IFile</code> from given <var>file</var>.
+     */
+    public final static IFile createFileNode(final java.io.File file)
+    {
+        assert file != null && file.isFile() : "Given file must be a file";
+        return new File(file);
+    }
+
+    /**
+     * Creates a <code>IDirectory</code> from given <var>file</var>.
+     */
+    public final static IDirectory createDirectoryNode(final java.io.File file)
+    {
+        assert file != null && file.isDirectory() : "Given file must be a directory";
+        return new Directory(file);
+    }
+
     private NodeFactory()
     {
         // Can not be instantiated.
diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/ChecksumBuilderTest.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/ChecksumBuilderTest.java
index 56291627349..a2806d8ffa1 100644
--- a/bds/sourceTest/java/ch/systemsx/cisd/bds/ChecksumBuilderTest.java
+++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/ChecksumBuilderTest.java
@@ -25,11 +25,10 @@ import java.io.IOException;
 import java.util.Random;
 
 import org.apache.commons.io.IOUtils;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import ch.systemsx.cisd.bds.storage.filesystem.FileStorage;
-import ch.systemsx.cisd.common.logging.LogInitializer;
+import ch.systemsx.cisd.common.utilities.AbstractFileSystemTestCase;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
 
 /**
@@ -37,43 +36,28 @@ import ch.systemsx.cisd.common.utilities.FileUtilities;
  * 
  * @author Christian Ribeaud
  */
-public class ChecksumBuilderTest
+public class ChecksumBuilderTest extends AbstractFileSystemTestCase
 {
-    private static final File UNIT_TEST_ROOT_DIRECTORY = new File("targets" + File.separator + "unit-test-wd");
-
-    private static final File WORKING_DIRECTORY =
-            new File(UNIT_TEST_ROOT_DIRECTORY, ChecksumBuilderTest.class.getSimpleName());
-
-    private ChecksumBuilder checksumBuilder;
-
-    @BeforeMethod
-    public final void setUp()
-    {
-        checksumBuilder = new ChecksumBuilder(new MD5ChecksumCalculator());
-        LogInitializer.init();
-        WORKING_DIRECTORY.mkdirs();
-        assert WORKING_DIRECTORY.isDirectory();
-        WORKING_DIRECTORY.deleteOnExit();
-    }
+    private ChecksumBuilder checksumBuilder = new ChecksumBuilder(new MD5ChecksumCalculator());
 
     @Test
     public final void testBuildChecksumsForAllFilesIn() throws Exception
     {
-        FileUtilities.writeToFile(new File(WORKING_DIRECTORY, "file1"), "This is my first file.");
-        FileUtilities.writeToFile(new File(WORKING_DIRECTORY, "abracadabra"), "This is my second file.");
-        fillWithRandomBytes(new File(WORKING_DIRECTORY, "bigBinaryFile"));
-        
-        File dir = new File(WORKING_DIRECTORY, "dir");
+        FileUtilities.writeToFile(new File(workingDirectory, "file1"), "This is my first file.");
+        FileUtilities.writeToFile(new File(workingDirectory, "abracadabra"), "This is my second file.");
+        fillWithRandomBytes(new File(workingDirectory, "bigBinaryFile"));
+
+        File dir = new File(workingDirectory, "dir");
         dir.mkdir();
         FileUtilities.writeToFile(new File(dir, "file2"), "This is my fourth file.");
         FileUtilities.writeToFile(new File(dir, "escargot.png"), "This is my fifth file.");
         FileUtilities.writeToFile(new File(dir, "barbapapa"), "This is my sixth file.");
-        
-        FileStorage fileStorage = new FileStorage(WORKING_DIRECTORY);
+
+        FileStorage fileStorage = new FileStorage(workingDirectory);
         fileStorage.mount();
-        
+
         String checksums = checksumBuilder.buildChecksumsForAllFilesIn(fileStorage.getRoot());
-        
+
         assertEquals("31b98e3c4af8d09595dcef2ce8232fbd  abracadabra" + LINE_SEPARATOR
                 + "90d87b30f70e265e9653356917320679  bigBinaryFile" + LINE_SEPARATOR
                 + "23b541115335bbe719c447bb9c942b1a  dir/barbapapa" + LINE_SEPARATOR
diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/UtilitiesTest.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/UtilitiesTest.java
index d16f1db54a4..908ba9ce5f4 100644
--- a/bds/sourceTest/java/ch/systemsx/cisd/bds/UtilitiesTest.java
+++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/UtilitiesTest.java
@@ -16,49 +16,33 @@
 
 package ch.systemsx.cisd.bds;
 
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.fail;
+
 import java.io.File;
 
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import ch.systemsx.cisd.bds.storage.IDirectory;
 import ch.systemsx.cisd.bds.storage.IFile;
 import ch.systemsx.cisd.bds.storage.filesystem.NodeFactory;
-import ch.systemsx.cisd.common.logging.LogInitializer;
-import ch.systemsx.cisd.common.utilities.FileUtilities;
-
-import static org.testng.AssertJUnit.*;
+import ch.systemsx.cisd.common.utilities.AbstractFileSystemTestCase;
 
 /**
  * Test cases for corresponding {@link Utilities} class.
  * 
  * @author Christian Ribeaud
  */
-public class UtilitiesTest
+public class UtilitiesTest extends AbstractFileSystemTestCase
 {
-    private static final File UNIT_TEST_ROOT_DIRECTORY = new File("targets" + File.separator + "unit-test-wd");
-
-    private static final File WORKING_DIRECTORY =
-            new File(UNIT_TEST_ROOT_DIRECTORY, UtilitiesTest.class.getSimpleName());
-
-    @BeforeMethod
-    public final void setUp()
-    {
-        LogInitializer.init();
-        FileUtilities.deleteRecursively(WORKING_DIRECTORY);
-        WORKING_DIRECTORY.mkdirs();
-        assert WORKING_DIRECTORY.isDirectory() && WORKING_DIRECTORY.listFiles().length == 0;
-        WORKING_DIRECTORY.deleteOnExit();
-    }
-
     @Test
     public final void testGetNumber()
     {
-        final IDirectory directory = (IDirectory) NodeFactory.createNode(WORKING_DIRECTORY);
+        final IDirectory directory = (IDirectory) NodeFactory.createNode(workingDirectory);
         final String key = "age";
         final String value = "35";
         final IFile file = directory.addKeyValuePair(key, value);
-        final File[] listFiles = WORKING_DIRECTORY.listFiles();
+        final File[] listFiles = workingDirectory.listFiles();
         assertEquals(1, listFiles.length);
         assertEquals(key, listFiles[0].getName());
         assertEquals(value, file.getStringContent().trim());
diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/StorageTestCase.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/ChannelListTest.java
similarity index 53%
rename from bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/StorageTestCase.java
rename to bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/ChannelListTest.java
index ef403ea8980..8612226cba7 100644
--- a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/StorageTestCase.java
+++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/ChannelListTest.java
@@ -14,28 +14,14 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.bds.storage.filesystem;
-
-import java.io.File;
-import java.io.IOException;
-
-import org.apache.commons.io.FileUtils;
-import org.testng.annotations.BeforeMethod;
+package ch.systemsx.cisd.bds.hcs;
 
 /**
- * An <code>abtract</code> storage test case.
+ * Test cases for corresponding {@link ChannelList} class.
  * 
- * @author Franz-Josef Elmer
+ * @author Christian Ribeaud
  */
-public abstract class StorageTestCase
+public final class ChannelListTest
 {
-    static final File TEST_DIR = new File("targets" + File.separator + "unit-test-wd");
-
-    @BeforeMethod
-    public void setup() throws IOException
-    {
-        TEST_DIR.mkdirs();
-        FileUtils.cleanDirectory(TEST_DIR);
-    }
 
-}
+}
\ No newline at end of file
diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/PlateGeometryTest.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/PlateGeometryTest.java
new file mode 100644
index 00000000000..46169eda698
--- /dev/null
+++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/PlateGeometryTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.hcs;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.AssertJUnit.*;
+
+import java.io.File;
+
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.bds.storage.IDirectory;
+import ch.systemsx.cisd.bds.storage.filesystem.NodeFactory;
+import ch.systemsx.cisd.common.utilities.AbstractFileSystemTestCase;
+
+/**
+ * Test cases for corresponding {@link PlateGeometry} class.
+ * 
+ * @author Christian Ribeaud
+ */
+public final class PlateGeometryTest extends AbstractFileSystemTestCase
+{
+
+    private final Geometry geometry = new PlateGeometry(2, 3);
+
+    @Test
+    public final void testConstructor()
+    {
+        try
+        {
+            new PlateGeometry(-1, 0);
+            fail("Rows and columns must be > 0.");
+        } catch (AssertionError ex)
+        {
+            // Nothing to do here.
+        }
+    }
+
+    @Test
+    public final void testEquals()
+    {
+        assertEquals(new PlateGeometry(2, 3), geometry);
+        assertFalse(new PlateGeometry(3, 2).equals(geometry));
+        assertFalse(geometry.equals(null));
+    }
+
+    @Test
+    public final void testSaveTo()
+    {
+        final IDirectory dir = NodeFactory.createDirectoryNode(workingDirectory);
+        geometry.saveTo(dir);
+        File[] files = workingDirectory.listFiles();
+        assertEquals(1, files.length);
+        final File geometryDir = files[0];
+        assertTrue(geometryDir.isDirectory());
+        assertEquals(PlateGeometry.PLATE_GEOMETRY, geometryDir.getName());
+        files = geometryDir.listFiles();
+        assertEquals(2, files.length);
+        assertEquals(PlateGeometry.COLUMNS, files[0].getName());
+        assertEquals(PlateGeometry.ROWS, files[1].getName());
+    }
+
+    @Test(dependsOnMethods = "testSaveTo")
+    public final void testLoadFrom()
+    {
+        testSaveTo();
+        final IDirectory dir = NodeFactory.createDirectoryNode(workingDirectory);
+        final Geometry loaded = PlateGeometry.loadFrom(dir);
+        assertNotNull(loaded);
+        assertTrue(loaded.getRows() == 2);
+        assertTrue(loaded.getColumns() == 3);
+    }
+}
diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/WellGeometryTest.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/WellGeometryTest.java
new file mode 100644
index 00000000000..142d4f763a1
--- /dev/null
+++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/hcs/WellGeometryTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.hcs;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertNotNull;
+import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.AssertJUnit.fail;
+
+import java.io.File;
+
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.bds.storage.IDirectory;
+import ch.systemsx.cisd.bds.storage.filesystem.NodeFactory;
+import ch.systemsx.cisd.common.utilities.AbstractFileSystemTestCase;
+
+/**
+ * Test cases for corresponding {@link WellGeometry} class.
+ * 
+ * @author Christian Ribeaud
+ */
+public final class WellGeometryTest extends AbstractFileSystemTestCase
+{
+    private final Geometry geometry = new WellGeometry(2, 3);
+
+    @Test
+    public final void testConstructor()
+    {
+        try
+        {
+            new WellGeometry(-1, 0);
+            fail("Rows and columns must be > 0.");
+        } catch (AssertionError ex)
+        {
+            // Nothing to do here.
+        }
+    }
+
+    @Test
+    public final void testEquals()
+    {
+        assertEquals(new WellGeometry(2, 3), geometry);
+        assertFalse(new WellGeometry(3, 2).equals(geometry));
+        assertFalse(geometry.equals(null));
+    }
+
+    @Test
+    public final void testSaveTo()
+    {
+        final IDirectory dir = NodeFactory.createDirectoryNode(workingDirectory);
+        geometry.saveTo(dir);
+        File[] files = workingDirectory.listFiles();
+        assertEquals(1, files.length);
+        final File geometryDir = files[0];
+        assertTrue(geometryDir.isDirectory());
+        assertEquals(WellGeometry.WELL_GEOMETRY, geometryDir.getName());
+        files = geometryDir.listFiles();
+        assertEquals(2, files.length);
+        assertEquals(WellGeometry.COLUMNS, files[0].getName());
+        assertEquals(WellGeometry.ROWS, files[1].getName());
+    }
+
+    @Test(dependsOnMethods = "testSaveTo")
+    public final void testLoadFrom()
+    {
+        testSaveTo();
+        final IDirectory dir = NodeFactory.createDirectoryNode(workingDirectory);
+        final Geometry loaded = WellGeometry.loadFrom(dir);
+        assertNotNull(loaded);
+        assertTrue(loaded.getRows() == 2);
+        assertTrue(loaded.getColumns() == 3);
+    }
+
+}
\ No newline at end of file
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 ef286616652..597de4c2dfd 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
@@ -31,6 +31,7 @@ import ch.systemsx.cisd.bds.storage.IDirectory;
 import ch.systemsx.cisd.bds.storage.IFile;
 import ch.systemsx.cisd.bds.storage.INode;
 import ch.systemsx.cisd.bds.storage.StorageException;
+import ch.systemsx.cisd.common.utilities.AbstractFileSystemTestCase;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
 
 /**
@@ -38,18 +39,18 @@ import ch.systemsx.cisd.common.utilities.FileUtilities;
  * 
  * @author Franz-Josef Elmer
  */
-public class DirectoryTest extends StorageTestCase
+public final class DirectoryTest extends AbstractFileSystemTestCase
 {
 
     private final void testInternalAddFile(final boolean move)
     {
-        File dir = new File(TEST_DIR, "dir");
+        File dir = new File(workingDirectory, "dir");
         dir.mkdirs();
         FileUtilities.writeToFile(new File(dir, "p1"), "property 1");
         File subDir = new File(dir, "subdir");
         subDir.mkdir();
         FileUtilities.writeToFile(new File(subDir, "p2"), "property 2");
-        File dest = new File(TEST_DIR, "destination");
+        File dest = new File(workingDirectory, "destination");
         dest.mkdir();
         Directory directory = new Directory(dest);
         INode copiedDir = directory.addFile(dir, move);
@@ -74,24 +75,24 @@ public class DirectoryTest extends StorageTestCase
         assertTrue(node instanceof IFile);
         assertEquals("property 2\n", ((IFile) node).getStringContent());
         assertEquals("property 2\n", FileUtilities.loadToString(new File(copiedRealSubDir, "p2")));
-        assertEquals(move == false, new File(TEST_DIR, "dir").exists());
+        assertEquals(move == false, new File(workingDirectory, "dir").exists());
     }
 
     @Test
     public void testGetName()
     {
-        Directory directory = new Directory(TEST_DIR);
-        assertEquals(TEST_DIR.getName(), directory.getName());
+        Directory directory = new Directory(workingDirectory);
+        assertEquals(workingDirectory.getName(), directory.getName());
     }
 
     @Test
     public void testMakeDirectory()
     {
-        Directory directory = new Directory(TEST_DIR);
+        Directory directory = new Directory(workingDirectory);
         IDirectory subdirectory = directory.makeDirectory("sub-directory");
 
         assertEquals("sub-directory", subdirectory.getName());
-        File subdir = new File(TEST_DIR, "sub-directory");
+        File subdir = new File(workingDirectory, "sub-directory");
         assertEquals(true, subdir.exists());
         assertEquals(true, subdir.isDirectory());
     }
@@ -99,12 +100,12 @@ public class DirectoryTest extends StorageTestCase
     @Test
     public void testMakeDirectoryTwice()
     {
-        Directory directory = new Directory(TEST_DIR);
+        Directory directory = new Directory(workingDirectory);
         directory.makeDirectory("sub-directory");
         IDirectory subdirectory = directory.makeDirectory("sub-directory");
 
         assertEquals("sub-directory", subdirectory.getName());
-        File subdir = new File(TEST_DIR, "sub-directory");
+        File subdir = new File(workingDirectory, "sub-directory");
         assertEquals(true, subdir.exists());
         assertEquals(true, subdir.isDirectory());
     }
@@ -112,7 +113,7 @@ public class DirectoryTest extends StorageTestCase
     @Test
     public void testMakeDirectoryButThereIsAlreadyAFileWithSameName()
     {
-        Directory directory = new Directory(TEST_DIR);
+        Directory directory = new Directory(workingDirectory);
         directory.addKeyValuePair("sub-directory", "value");
         try
         {
@@ -127,21 +128,21 @@ public class DirectoryTest extends StorageTestCase
     @Test
     public void testAddKeyValuePair()
     {
-        IFile stringFile = new Directory(TEST_DIR).addKeyValuePair("answer", "42");
-        assertEquals("42\n", FileUtilities.loadToString(new File(TEST_DIR, "answer")));
+        IFile stringFile = new Directory(workingDirectory).addKeyValuePair("answer", "42");
+        assertEquals("42\n", FileUtilities.loadToString(new File(workingDirectory, "answer")));
         assertEquals("42\n", stringFile.getStringContent());
     }
 
     @Test
     public void testExtractTo()
     {
-        File dir = new File(TEST_DIR, "dir");
+        File dir = new File(workingDirectory, "dir");
         dir.mkdirs();
         Directory directory = new Directory(dir);
         directory.addKeyValuePair("p1", "property 1");
         IDirectory subdir = directory.makeDirectory("subdir");
         subdir.addKeyValuePair("p2", "property 2");
-        File destination = new File(TEST_DIR, "destination");
+        File destination = new File(workingDirectory, "destination");
         assertFalse(destination.exists());
         directory.extractTo(destination);
         assertTrue(destination.exists());
@@ -152,31 +153,31 @@ public class DirectoryTest extends StorageTestCase
         assertTrue(copiedSubDir.isDirectory());
         assertEquals("property 2\n", FileUtilities.loadToString(new File(copiedSubDir, "p2")));
         // Source directory still exists
-        assertEquals(true, new File(TEST_DIR, "dir").exists());
+        assertEquals(true, new File(workingDirectory, "dir").exists());
     }
 
     @Test
     public void testMoveTo()
     {
-        File dir = new File(TEST_DIR, "dir");
+        File dir = new File(workingDirectory, "dir");
         dir.mkdirs();
         IDirectory directory = new Directory(dir);
         directory.addKeyValuePair("p1", "property 1");
         IDirectory subdir = directory.makeDirectory("subdir");
         subdir.addKeyValuePair("p2", "property 2");
-        subdir.moveTo(TEST_DIR);
-        
+        subdir.moveTo(workingDirectory);
+
         Iterator<INode> iterator = directory.iterator();
         assertEquals(true, iterator.hasNext());
         INode node = iterator.next();
         assertEquals("p1", node.getName());
         assertEquals("property 1", ((IFile) node).getStringContent().trim());
         assertEquals(false, iterator.hasNext());
-        File subdir2 = new File(TEST_DIR, "subdir");
+        File subdir2 = new File(workingDirectory, "subdir");
         assertEquals(true, subdir2.isDirectory());
         assertEquals("property 2", FileUtilities.loadToString(new File(subdir2, "p2")).trim());
     }
-    
+
     @Test
     public void testAddFileWithCopy()
     {
diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/FileStorageTest.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/FileStorageTest.java
index 60dfaf09542..a17ec92b676 100644
--- a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/FileStorageTest.java
+++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/FileStorageTest.java
@@ -22,26 +22,27 @@ import org.testng.AssertJUnit;
 import org.testng.annotations.Test;
 
 import ch.systemsx.cisd.bds.storage.StorageException;
+import ch.systemsx.cisd.common.utilities.AbstractFileSystemTestCase;
 
 /**
+ * Test cases for corresponding {@link FileStorage} class.
  * 
- *
  * @author Franz-Josef Elmer
  */
-public class FileStorageTest extends StorageTestCase
+public final class FileStorageTest extends AbstractFileSystemTestCase
 {
     @Test
     public void testGetRoot()
     {
-        FileStorage fileStorage = new FileStorage(TEST_DIR);
+        FileStorage fileStorage = new FileStorage(workingDirectory);
         fileStorage.mount();
-        assertEquals(TEST_DIR.getName(), fileStorage.getRoot().getName());
+        assertEquals(workingDirectory.getName(), fileStorage.getRoot().getName());
     }
-    
+
     @Test
     public void testGetRootOfNeverMountedStorage()
     {
-        FileStorage fileStorage = new FileStorage(TEST_DIR);
+        FileStorage fileStorage = new FileStorage(workingDirectory);
         try
         {
             fileStorage.getRoot();
@@ -51,11 +52,11 @@ public class FileStorageTest extends StorageTestCase
             assertEquals("Can not get root of an unmounted storage.", e.getMessage());
         }
     }
-    
+
     @Test
     public void testGetRootOfUnMountedStorage()
     {
-        FileStorage fileStorage = new FileStorage(TEST_DIR);
+        FileStorage fileStorage = new FileStorage(workingDirectory);
         fileStorage.mount();
         fileStorage.unmount();
         try
diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/FileTest.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/FileTest.java
index 1de18aeec8e..e834a0f16b9 100644
--- a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/FileTest.java
+++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/FileTest.java
@@ -27,17 +27,20 @@ import org.testng.annotations.Test;
 
 import ch.systemsx.cisd.bds.storage.IDirectory;
 import ch.systemsx.cisd.bds.storage.IFile;
+import ch.systemsx.cisd.common.utilities.AbstractFileSystemTestCase;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
 
 /**
+ * Test cases for corresponding {@link File} class.
+ * 
  * @author Franz-Josef Elmer
  */
-public class FileTest extends StorageTestCase
+public final class FileTest extends AbstractFileSystemTestCase
 {
     @Test
     public void testGetValueAndGetName()
     {
-        java.io.File file = new java.io.File(TEST_DIR, "test.txt");
+        java.io.File file = new java.io.File(workingDirectory, "test.txt");
         FileUtilities.writeToFile(file, "Hello\nworld!\n");
         File stringFile = new File(file);
 
@@ -49,11 +52,11 @@ public class FileTest extends StorageTestCase
     @Test
     public void testExtractTo()
     {
-        java.io.File file = new java.io.File(TEST_DIR, "test.txt");
+        java.io.File file = new java.io.File(workingDirectory, "test.txt");
         FileUtilities.writeToFile(file, "Hello\nworld!\n");
         File stringFile = new File(file);
 
-        java.io.File subdir = new java.io.File(TEST_DIR, "subdir");
+        java.io.File subdir = new java.io.File(workingDirectory, "subdir");
         subdir.mkdir();
         stringFile.extractTo(subdir);
         assertEquals("Hello\nworld!\n", FileUtilities.loadToString(new java.io.File(subdir, stringFile.getName())));
@@ -62,26 +65,27 @@ public class FileTest extends StorageTestCase
     @Test
     public void testMoveTo()
     {
-        java.io.File dir = new java.io.File(TEST_DIR, "dir");
+        java.io.File dir = new java.io.File(workingDirectory, "dir");
         dir.mkdirs();
         IDirectory directory = new Directory(dir);
         IFile file = directory.addKeyValuePair("p1", "property 1");
-        
-        file.moveTo(TEST_DIR);
-        
+
+        file.moveTo(workingDirectory);
+
         assertEquals(false, directory.iterator().hasNext());
-        assertEquals("property 1", FileUtilities.loadToString(new java.io.File(TEST_DIR, "p1")).trim());
+        assertEquals("property 1", FileUtilities.loadToString(new java.io.File(workingDirectory, "p1")).trim());
     }
-    
+
     @Test
     public void testGetInputStream() throws Exception
     {
-        java.io.File file = new java.io.File(TEST_DIR, "test");
+        java.io.File file = new java.io.File(workingDirectory, "test");
         FileOutputStream fileOutputStream = null;
         try
         {
             fileOutputStream = new FileOutputStream(file);
-            fileOutputStream.write(new byte[] {1, 2, 3, 4});
+            fileOutputStream.write(new byte[]
+                { 1, 2, 3, 4 });
         } catch (IOException ex)
         {
             throw ex;
@@ -89,7 +93,7 @@ public class FileTest extends StorageTestCase
         {
             IOUtils.closeQuietly(fileOutputStream);
         }
-        
+
         File binaryFile = new File(file);
         InputStream inputStream = binaryFile.getInputStream();
         try
diff --git a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/NodeFactoryTest.java b/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/NodeFactoryTest.java
index ff9b70135ed..330335e396a 100644
--- a/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/NodeFactoryTest.java
+++ b/bds/sourceTest/java/ch/systemsx/cisd/bds/storage/filesystem/NodeFactoryTest.java
@@ -24,19 +24,20 @@ import org.testng.annotations.Test;
 import ch.systemsx.cisd.bds.storage.IDirectory;
 import ch.systemsx.cisd.bds.storage.IFile;
 import ch.systemsx.cisd.bds.storage.INode;
+import ch.systemsx.cisd.common.utilities.AbstractFileSystemTestCase;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
 
 /**
- * 
+ * Test cases for corresponding {@link NodeFactory} class.
  *
  * @author Franz-Josef Elmer
  */
-public class NodeFactoryTest extends StorageTestCase
+public class NodeFactoryTest extends AbstractFileSystemTestCase
 {
     @Test
     public void testCreateFileNode()
     {
-        java.io.File file = new java.io.File(TEST_DIR, "text.txt");
+        java.io.File file = new java.io.File(workingDirectory, "text.txt");
         FileUtilities.writeToFile(file, "hello");
         INode node = NodeFactory.createNode(file);
         assertTrue(node instanceof IFile);
@@ -46,7 +47,7 @@ public class NodeFactoryTest extends StorageTestCase
     @Test
     public void testCreateDirectoryNode()
     {
-        java.io.File file = new java.io.File(TEST_DIR, "dir");
+        java.io.File file = new java.io.File(workingDirectory, "dir");
         file.mkdir();
         INode node = NodeFactory.createNode(file);
         assertTrue(node instanceof IDirectory);
-- 
GitLab