From e3a9494d58fd4162875cbfd5b08c98293e4d3002 Mon Sep 17 00:00:00 2001
From: tpylak <tpylak>
Date: Tue, 30 Oct 2007 07:28:44 +0000
Subject: [PATCH] tab file loader for properties

SVN: 2283
---
 .../parser/AbstractParserObjectFactory.java   |  32 +----
 .../parser/AbstractPropertiesSetter.java      | 131 ++++++++++++++++++
 .../cisd/common/parser/IPropertiesSetter.java |  30 ++++
 .../common/parser/IPropertyObjectFactory.java |  26 ++++
 .../cisd/common/parser/ParseException.java    |   6 -
 .../parser/PropertiesParserObjectFactory.java |  65 +++++++++
 .../cisd/common/parser/TabFileLoader.java     |   8 +-
 .../cisd/common/utilities/ClassUtils.java     |  30 +++-
 8 files changed, 287 insertions(+), 41 deletions(-)
 create mode 100644 common/source/java/ch/systemsx/cisd/common/parser/AbstractPropertiesSetter.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/parser/IPropertiesSetter.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/parser/IPropertyObjectFactory.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/parser/PropertiesParserObjectFactory.java

diff --git a/common/source/java/ch/systemsx/cisd/common/parser/AbstractParserObjectFactory.java b/common/source/java/ch/systemsx/cisd/common/parser/AbstractParserObjectFactory.java
index 26eca52fb3b..6bb8ecb4c7f 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/AbstractParserObjectFactory.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/AbstractParserObjectFactory.java
@@ -17,14 +17,11 @@
 package ch.systemsx.cisd.common.parser;
 
 import java.beans.PropertyDescriptor;
-import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -51,8 +48,8 @@ public abstract class AbstractParserObjectFactory<E> implements IParserObjectFac
      */
     private final Map<String, PropertyDescriptor> propertyDescriptors;
 
-    /** The list of mandatory <code>Field</code>s for {@link NewExperiment}, keyed by their name. */
-    private final Map<String, Field> mandatoryFields;
+    /** The set of mandatory field names for {@link NewExperiment} */
+    private final Set<String> mandatoryFields;
 
     /** The pool of {@link Converter}s. */
     private final ConverterPool converterPool;
@@ -65,7 +62,8 @@ public abstract class AbstractParserObjectFactory<E> implements IParserObjectFac
         assert beanClass != null;
         assert propertyMapper != null;
         propertyDescriptors = BeanUtils.getPropertyDescriptors(beanClass);
-        mandatoryFields = createMandatoryFields(beanClass);
+        mandatoryFields = ClassUtils.getMandatoryFields(beanClass);
+        assert mandatoryFields != null;
         checkPropertyMapper(beanClass, propertyMapper);
         this.propertyMapper = propertyMapper;
         converterPool = createConverterPool();
@@ -126,8 +124,8 @@ public abstract class AbstractParserObjectFactory<E> implements IParserObjectFac
         propertyNames.removeAll(propertyDescriptors.keySet());
         if (propertyNames.size() > 0)
         {
-            throw UserFailureException.fromTemplate("The following header columns are not part of '%s': %s",
-                    clazz.getSimpleName(), format(propertyNames));
+            throw UserFailureException.fromTemplate("The following header columns are not part of '%s': %s", clazz
+                    .getSimpleName(), format(propertyNames));
         }
     }
 
@@ -144,27 +142,11 @@ public abstract class AbstractParserObjectFactory<E> implements IParserObjectFac
         builder.setLength(builder.length() - 2);
         return builder.toString();
     }
-    
-    /**
-     * Analyzes given <code>Class</code> and returns a <code>Map</code> containing the mandatory <code>Field</code>s
-     * keyed by {@link Field#getName()}.
-     */
-    private final static Map<String, Field> createMandatoryFields(Class<?> clazz)
-    {
-        Map<String, Field> map = new HashMap<String, Field>();
-        List<Field> fields = ClassUtils.getMandatoryFields(clazz);
-        for (Field field : fields)
-        {
-            map.put(field.getName(), field);
-        }
-        return map;
-    }
 
     /** Whether given field name is mandatory. */
     private final boolean isMandatory(String fieldName)
     {
-        assert mandatoryFields != null;
-        return mandatoryFields.containsKey(fieldName);
+        return mandatoryFields.contains(fieldName);
     }
 
     private String getPropertyValue(final String[] lineTokens, final IPropertyModel propertyModel)
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/AbstractPropertiesSetter.java b/common/source/java/ch/systemsx/cisd/common/parser/AbstractPropertiesSetter.java
new file mode 100644
index 00000000000..c63feb4165c
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/parser/AbstractPropertiesSetter.java
@@ -0,0 +1,131 @@
+/*
+ * 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.common.parser;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.common.utilities.ClassUtils;
+
+/**
+ * super class for constructing and holding key-value pairs
+ * 
+ * @author Tomasz Pylak on Oct 26, 2007
+ */
+abstract public class AbstractPropertiesSetter<ConstructedType> implements IPropertiesSetter<ConstructedType>
+{
+    abstract public ConstructedType done();
+
+    // ---------------------
+
+    private final Map<String, String> properties; // map: property name -> valueOrNull
+
+    private final Set<String> mandatoryFields;
+
+    private final Set<String> availableProperties;
+
+    protected AbstractPropertiesSetter(Class<?> beanClass)
+    {
+        this.properties = new HashMap<String, String>();
+        this.mandatoryFields = toLowerCase(ClassUtils.getMandatoryFields(beanClass));
+        this.availableProperties = toLowerCase(getAvailableProperties(beanClass));
+    }
+
+    private static Set<String> toLowerCase(Set<String> set)
+    {
+        Set<String> result = new HashSet<String>();
+        for (String elem : set)
+        {
+            result.add(elem.toLowerCase());
+        }
+        return result;
+    }
+
+    public void setProperty(String name, String valueOrNull)
+    {
+        String propName = name.toLowerCase();
+        Boolean mandatory = isMandatory(propName);
+        if (mandatory != null)
+        {
+            if (mandatory == true && valueOrNull == null)
+            {
+                throw createMandatoryException(propName);
+            }
+            properties.put(propName, valueOrNull);
+        } else
+        {
+            throw UserFailureException.fromTemplate("Unknown property name '%s', failed to set value to '%s'",
+                    propName, valueOrNull);
+        }
+    }
+
+    private static UserFailureException createMandatoryException(String name)
+    {
+        return UserFailureException.fromTemplate("Property '%s' is mandatory and cannot be set to the empty value.",
+                name);
+    }
+
+    // temporary method, this information will be fetched from db
+    private Set<String> getAvailableProperties(Class<?> clazz)
+    {
+        Set<String> fieldNames = new HashSet<String>();
+        for (Field field : clazz.getDeclaredFields())
+        {
+            fieldNames.add(field.getName());
+        }
+        Class<?> superclass = clazz.getSuperclass();
+        if (superclass != null)
+        {
+            Set<String> superFieldNames = getAvailableProperties(superclass);
+            fieldNames.addAll(superFieldNames);
+        }
+        return fieldNames;
+    }
+
+    /** return true if property is mandatory, false if it is optional and null if the property does not exist */
+    private Boolean isMandatory(String name)
+    {
+        if (availableProperties.contains(name))
+        {
+            return mandatoryFields.contains(name);
+        } else
+        {
+            return null;
+        }
+    }
+
+    protected void checkMandatoryConstraint()
+    {
+        for (String name : availableProperties)
+        {
+            String value = properties.get(name);
+            if (mandatoryFields.contains(name) && value == null)
+            {
+                throw createMandatoryException(name);
+            }
+        }
+    }
+
+    protected String tryGetValue(String name)
+    {
+        return properties.get(name.toLowerCase());
+    }
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/IPropertiesSetter.java b/common/source/java/ch/systemsx/cisd/common/parser/IPropertiesSetter.java
new file mode 100644
index 00000000000..7d069e62539
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/parser/IPropertiesSetter.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.parser;
+
+/**
+ * Used to construct object by setting its properties
+ * 
+ * @author Tomasz Pylak on Oct 29, 2007
+ */
+public interface IPropertiesSetter<ConstructedType>
+{
+    void setProperty(String name, String valueOrNull);
+
+    /** returns constructed object, called after setting properties values */
+    ConstructedType done();
+}
\ No newline at end of file
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/IPropertyObjectFactory.java b/common/source/java/ch/systemsx/cisd/common/parser/IPropertyObjectFactory.java
new file mode 100644
index 00000000000..fed3643981b
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/parser/IPropertyObjectFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.common.parser;
+
+/**
+ * @author Tomasz Pylak on Oct 26, 2007
+ */
+public interface IPropertyObjectFactory<E>
+{
+    /** @return properties setter which can be used to construct a new object */
+    IPropertiesSetter<E> createObjectSetter();
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/ParseException.java b/common/source/java/ch/systemsx/cisd/common/parser/ParseException.java
index 6af27a11dae..f8aa5ea362e 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/ParseException.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/ParseException.java
@@ -30,12 +30,6 @@ public final class ParseException extends UserFailureException
 
     private final int lineNumber;
 
-    public ParseException(String message, int lineNumber)
-    {
-        super(message);
-        this.lineNumber = lineNumber;
-    }
-
     public ParseException(String message, Throwable cause, int lineNumber)
     {
         super(message, cause);
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/PropertiesParserObjectFactory.java b/common/source/java/ch/systemsx/cisd/common/parser/PropertiesParserObjectFactory.java
new file mode 100644
index 00000000000..2ba4ac359eb
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/parser/PropertiesParserObjectFactory.java
@@ -0,0 +1,65 @@
+/*
+ * 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.common.parser;
+
+import java.util.Set;
+
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+
+/**
+ * Creates new object by setting its properties.
+ * 
+ * @author Tomasz Pylak on Oct 26, 2007
+ */
+public class PropertiesParserObjectFactory<E> implements IParserObjectFactory<E>
+{
+    /** The <code>IPropertyMapper</code> implementation. */
+    private final IPropertyMapper propertyMapper;
+
+    private final IPropertyObjectFactory<E> objectFactory;
+
+    public PropertiesParserObjectFactory(IPropertyMapper propertyMapper, IPropertyObjectFactory<E> objectFactory)
+    {
+        this.propertyMapper = propertyMapper;
+        this.objectFactory = objectFactory;
+    }
+
+    public E createObject(String[] lineTokens) throws UserFailureException
+    {
+        IPropertiesSetter<E> setter = objectFactory.createObjectSetter();
+        Set<String> propertyNames = propertyMapper.getAllPropertyNames();
+        for (String name : propertyNames)
+        {
+            final IPropertyModel propertyModel = propertyMapper.getProperty(name);
+            String propertyValue = getPropertyValue(lineTokens, propertyModel);
+            setter.setProperty(name, propertyValue);
+        }
+        return setter.done();
+    }
+
+    private String getPropertyValue(final String[] lineTokens, final IPropertyModel propertyModel)
+    {
+        int column = propertyModel.getColumn();
+        if (column >= lineTokens.length)
+        {
+            throw UserFailureException.fromTemplate("Not enough tokens are available (index: %d, available: %d)",
+                    column, lineTokens.length);
+        }
+        return lineTokens[column];
+    }
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/parser/TabFileLoader.java b/common/source/java/ch/systemsx/cisd/common/parser/TabFileLoader.java
index 2e6698480ae..de522c599d5 100644
--- a/common/source/java/ch/systemsx/cisd/common/parser/TabFileLoader.java
+++ b/common/source/java/ch/systemsx/cisd/common/parser/TabFileLoader.java
@@ -29,12 +29,12 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 
 /**
  * Convenient class to load a tab file and deliver a list of beans of type <code>T</code>.
- *
+ * 
  * @author Franz-Josef Elmer
  */
 public class TabFileLoader<T>
 {
-    
+
     private final IParserObjectFactoryFactory<T> factory;
 
     /**
@@ -49,8 +49,8 @@ public class TabFileLoader<T>
     /**
      * Loads from the specified tab file a list of objects of type <code>T</code>.
      * 
-     * @throws UserFailureException if the file does not exists, the header line with correct column 
-     *      bean attribute names is missing, or a parsing error occurs.
+     * @throws UserFailureException if the file does not exists, the header line with correct column bean attribute
+     *             names is missing, or a parsing error occurs.
      * @throws EnvironmentFailureException if a IOException occured.
      */
     public List<T> load(File file)
diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/ClassUtils.java b/common/source/java/ch/systemsx/cisd/common/utilities/ClassUtils.java
index 625fde0bf8b..20f3240af63 100644
--- a/common/source/java/ch/systemsx/cisd/common/utilities/ClassUtils.java
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/ClassUtils.java
@@ -21,8 +21,10 @@ import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Properties;
+import java.util.Set;
 
 import ch.systemsx.cisd.common.annotation.Mandatory;
 import ch.systemsx.cisd.common.exceptions.CheckedExceptionTunnel;
@@ -40,10 +42,24 @@ public final class ClassUtils
         // Can not be instantiated.
     }
 
+    /**
+     * For given <code>Class</code> returns a set of field names that are annotated with {@link Mandatory}.
+     */
+    public final static Set<String> getMandatoryFields(Class<?> clazz)
+    {
+        Set<String> set = new HashSet<String>();
+        List<Field> fields = ClassUtils.getMandatoryFieldsList(clazz);
+        for (Field field : fields)
+        {
+            set.add(field.getName());
+        }
+        return set;
+    }
+
     /**
      * For given <code>Class</code> returns a list of fields that are annotated with {@link Mandatory}.
      */
-    public final static List<Field> getMandatoryFields(Class<?> clazz)
+    private final static List<Field> getMandatoryFieldsList(Class<?> clazz)
     {
         return getMandatoryFields(clazz, null);
     }
@@ -142,7 +158,7 @@ public final class ClassUtils
     {
         assert superClazz != null : "Missing super class";
         assert className != null : "Missing class name";
-        
+
         try
         {
             final Class<?> clazz = Class.forName(className);
@@ -152,7 +168,8 @@ public final class ClassUtils
             {
                 return createInstance(clazz);
             }
-            final Constructor<?> constructor = clazz.getConstructor(new Class[] {Properties.class});
+            final Constructor<?> constructor = clazz.getConstructor(new Class[]
+                { Properties.class });
             return createInstance(constructor, properties);
         } catch (Exception ex)
         {
@@ -165,12 +182,13 @@ public final class ClassUtils
     {
         return (T) clazz.newInstance();
     }
-    
+
     @SuppressWarnings("unchecked")
     private static <T> T createInstance(final Constructor<?> constructor, Properties properties)
             throws InstantiationException, IllegalAccessException, InvocationTargetException
     {
-        return (T) constructor.newInstance(new Object[] {properties});
+        return (T) constructor.newInstance(new Object[]
+            { properties });
     }
-    
+
 }
-- 
GitLab