From 0537ceb7c70cc0b8159cb2a8d3004502678f6222 Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Thu, 13 Feb 2020 23:41:00 +0100
Subject: [PATCH] BUG: handling of empty text values and null values

---
 .../caosdb/datetime/SemiCompleteDateTime.java |   6 -
 .../java/caosdb/datetime/UTCDateTime.java     |   6 -
 .../transaction/InsertEntityProperties.java   |  25 +-
 .../server/datatype/AbstractEnumValue.java    |   6 -
 .../server/datatype/CollectionValue.java      |  31 +-
 .../caosdb/server/datatype/GenericValue.java  |  13 +-
 .../server/datatype/IndexedSingleValue.java   |   8 -
 .../caosdb/server/datatype/ListDatatype.java  |   9 +-
 .../server/datatype/ReferenceValue.java       |   6 -
 .../caosdb/server/datatype/SingleValue.java   |   4 -
 .../server/datatype/TimespanDatatype.java     | 291 +++++++++---------
 .../java/caosdb/server/entity/Entity.java     |  17 +-
 .../entity/xml/PropertyToElementStrategy.java |   3 +-
 13 files changed, 206 insertions(+), 219 deletions(-)

diff --git a/src/main/java/caosdb/datetime/SemiCompleteDateTime.java b/src/main/java/caosdb/datetime/SemiCompleteDateTime.java
index 9082e5bf..55a7f5d5 100644
--- a/src/main/java/caosdb/datetime/SemiCompleteDateTime.java
+++ b/src/main/java/caosdb/datetime/SemiCompleteDateTime.java
@@ -24,7 +24,6 @@ package caosdb.datetime;
 
 import caosdb.server.datatype.AbstractDatatype.Table;
 import java.util.TimeZone;
-import org.jdom2.Attribute;
 import org.jdom2.Element;
 
 public class SemiCompleteDateTime extends FragmentDateTime implements Interval {
@@ -47,11 +46,6 @@ public class SemiCompleteDateTime extends FragmentDateTime implements Interval {
     throw new UnsupportedOperationException("This DateTime cannot be stored to the database.");
   }
 
-  @Override
-  public void addToAttribute(final Attribute a) {
-    throw new UnsupportedOperationException("This DateTime cannot be added to an xml attribute.");
-  }
-
   @Override
   public String toDateTimeString(final TimeZone tz) {
     final StringBuilder sb = new StringBuilder();
diff --git a/src/main/java/caosdb/datetime/UTCDateTime.java b/src/main/java/caosdb/datetime/UTCDateTime.java
index 72a55faa..215de67b 100644
--- a/src/main/java/caosdb/datetime/UTCDateTime.java
+++ b/src/main/java/caosdb/datetime/UTCDateTime.java
@@ -27,7 +27,6 @@ import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.TimeZone;
-import org.jdom2.Attribute;
 import org.jdom2.Element;
 
 public class UTCDateTime implements Interval {
@@ -292,11 +291,6 @@ public class UTCDateTime implements Interval {
     return sb.toString();
   }
 
-  @Override
-  public void addToAttribute(final Attribute a) {
-    a.setValue(toDateTimeString(TimeZone.getDefault()));
-  }
-
   @Override
   public String toString() {
     throw new NullPointerException("toString method!!!");
diff --git a/src/main/java/caosdb/server/database/backend/transaction/InsertEntityProperties.java b/src/main/java/caosdb/server/database/backend/transaction/InsertEntityProperties.java
index 05e4dbce..a777d420 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/InsertEntityProperties.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/InsertEntityProperties.java
@@ -30,6 +30,7 @@ import caosdb.server.database.proto.FlatProperty;
 import caosdb.server.datatype.AbstractCollectionDatatype;
 import caosdb.server.datatype.AbstractDatatype.Table;
 import caosdb.server.datatype.CollectionValue;
+import caosdb.server.datatype.IndexedSingleValue;
 import caosdb.server.datatype.SingleValue;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.Role;
@@ -37,6 +38,7 @@ import caosdb.server.entity.StatementStatus;
 import caosdb.server.entity.wrapper.Property;
 import java.util.ArrayList;
 import java.util.Deque;
+import java.util.Iterator;
 import java.util.List;
 
 public class InsertEntityProperties extends BackendTransaction {
@@ -111,22 +113,33 @@ public class InsertEntityProperties extends BackendTransaction {
           if (property.getValue() instanceof CollectionValue) {
             // insert collection of values
             final CollectionValue v = (CollectionValue) property.getValue();
+            Iterator<IndexedSingleValue> iterator = v.iterator();
+            final SingleValue firstValue = iterator.next();
 
             // insert 2nd to nth item
             for (int i = 1; i < v.size(); i++) {
-              final SingleValue vi = v.get(i);
-              table = (vi != null ? vi.getTable() : Table.null_data);
+              final SingleValue vi = iterator.next();
               fp.idx = i;
-              fp.value = (vi != null ? vi.toDatabaseString() : null);
+              if (vi == null) {
+                fp.value = null;
+                table = Table.null_data;
+              } else {
+                fp.value = vi.toDatabaseString();
+                table = vi.getTable();
+              }
               t.execute(
                   domain, (entity != null ? entity : property.getDomain()), fp, table, unit_sig);
             }
 
             // insert first item
-            final SingleValue vi = v.get(0);
             fp.idx = 0;
-            fp.value = vi.toDatabaseString();
-            table = (vi != null ? vi.getTable() : Table.null_data);
+            if (firstValue == null) {
+              fp.value = null;
+              table = Table.null_data;
+            } else {
+              fp.value = firstValue.toDatabaseString();
+              table = firstValue.getTable();
+            }
 
           } else {
             // insert single value
diff --git a/src/main/java/caosdb/server/datatype/AbstractEnumValue.java b/src/main/java/caosdb/server/datatype/AbstractEnumValue.java
index 9dd3a687..33303f22 100644
--- a/src/main/java/caosdb/server/datatype/AbstractEnumValue.java
+++ b/src/main/java/caosdb/server/datatype/AbstractEnumValue.java
@@ -24,7 +24,6 @@ package caosdb.server.datatype;
 
 import caosdb.server.datatype.AbstractDatatype.Table;
 import com.google.common.base.Objects;
-import org.jdom2.Attribute;
 import org.jdom2.Element;
 
 public abstract class AbstractEnumValue implements SingleValue {
@@ -50,11 +49,6 @@ public abstract class AbstractEnumValue implements SingleValue {
     return this.value.toString();
   }
 
-  @Override
-  public final void addToAttribute(final Attribute a) {
-    a.setValue(this.value.toString());
-  }
-
   @Override
   public boolean equals(final Object obj) {
     if (obj instanceof AbstractEnumValue) {
diff --git a/src/main/java/caosdb/server/datatype/CollectionValue.java b/src/main/java/caosdb/server/datatype/CollectionValue.java
index 65c8fa5f..3e70de73 100644
--- a/src/main/java/caosdb/server/datatype/CollectionValue.java
+++ b/src/main/java/caosdb/server/datatype/CollectionValue.java
@@ -22,30 +22,30 @@
  */
 package caosdb.server.datatype;
 
-import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
 import org.jdom2.Element;
 
-public class CollectionValue extends LinkedList<IndexedSingleValue> implements Value {
+public class CollectionValue implements Value, Iterable<IndexedSingleValue> {
 
-  private static final long serialVersionUID = 1L;
+  private List<IndexedSingleValue> list = new ArrayList<>();
 
   @Override
   public void addToElement(final Element e) {
     for (final SingleValue v : this) {
       final Element valueElem = new Element("Value");
-      if (v != null) {
-        v.addToElement(valueElem);
-      }
+      v.addToElement(valueElem);
       e.addContent(valueElem);
     }
   }
 
-  @Override
   public boolean add(final IndexedSingleValue e) {
     if (e == null) {
-      return super.add(new IndexedSingleValue(null));
+      return list.add(new IndexedSingleValue(null));
     }
-    return super.add(e);
+    return list.add(e);
   }
 
   public void add(final SingleValue v) {
@@ -67,4 +67,17 @@ public class CollectionValue extends LinkedList<IndexedSingleValue> implements V
       throw new IllegalArgumentException("Expected a SingleValue.");
     }
   }
+
+  @Override
+  public Iterator<IndexedSingleValue> iterator() {
+    return list.iterator();
+  }
+
+  public void sort() {
+    Collections.sort(list);
+  }
+
+  public int size() {
+    return list.size();
+  }
 }
diff --git a/src/main/java/caosdb/server/datatype/GenericValue.java b/src/main/java/caosdb/server/datatype/GenericValue.java
index 8e8f1dd4..cffd62bb 100644
--- a/src/main/java/caosdb/server/datatype/GenericValue.java
+++ b/src/main/java/caosdb/server/datatype/GenericValue.java
@@ -24,7 +24,6 @@ package caosdb.server.datatype;
 
 import caosdb.server.datatype.AbstractDatatype.Table;
 import com.google.common.base.Objects;
-import org.jdom2.Attribute;
 import org.jdom2.Element;
 
 public class GenericValue implements SingleValue {
@@ -61,7 +60,12 @@ public class GenericValue implements SingleValue {
 
   @Override
   public void addToElement(final Element e) {
-    e.addContent(this.value.toString());
+    if (this.value instanceof String && ((String) this.value).isEmpty()) {
+      Element empty = new Element("EmptyString");
+      e.addContent(empty);
+    } else {
+      e.addContent(this.value.toString());
+    }
   }
 
   @Override
@@ -74,11 +78,6 @@ public class GenericValue implements SingleValue {
     return this.value.toString();
   }
 
-  @Override
-  public void addToAttribute(final Attribute a) {
-    a.setValue(this.value.toString());
-  }
-
   @Override
   public String toString() {
     return toDatabaseString();
diff --git a/src/main/java/caosdb/server/datatype/IndexedSingleValue.java b/src/main/java/caosdb/server/datatype/IndexedSingleValue.java
index 01858228..f33db43e 100644
--- a/src/main/java/caosdb/server/datatype/IndexedSingleValue.java
+++ b/src/main/java/caosdb/server/datatype/IndexedSingleValue.java
@@ -23,7 +23,6 @@
 package caosdb.server.datatype;
 
 import caosdb.server.datatype.AbstractDatatype.Table;
-import org.jdom2.Attribute;
 import org.jdom2.Element;
 
 public class IndexedSingleValue implements SingleValue, Comparable<IndexedSingleValue> {
@@ -63,13 +62,6 @@ public class IndexedSingleValue implements SingleValue, Comparable<IndexedSingle
     return this.wrapped.toDatabaseString();
   }
 
-  @Override
-  public void addToAttribute(final Attribute a) {
-    if (this.wrapped != null) {
-      this.wrapped.addToAttribute(a);
-    }
-  }
-
   public void setIndex(final int i) {
     this.index = i;
   }
diff --git a/src/main/java/caosdb/server/datatype/ListDatatype.java b/src/main/java/caosdb/server/datatype/ListDatatype.java
index e61a1a3b..87790145 100644
--- a/src/main/java/caosdb/server/datatype/ListDatatype.java
+++ b/src/main/java/caosdb/server/datatype/ListDatatype.java
@@ -23,7 +23,6 @@
 package caosdb.server.datatype;
 
 import caosdb.server.entity.Message;
-import java.util.Collections;
 
 public class ListDatatype extends AbstractCollectionDatatype {
 
@@ -38,9 +37,13 @@ public class ListDatatype extends AbstractCollectionDatatype {
     final CollectionValue ret = new CollectionValue();
     if (value instanceof CollectionValue) {
       final CollectionValue colValue = (CollectionValue) value;
-      Collections.sort(colValue);
+      colValue.sort();
       for (final IndexedSingleValue singleValue : colValue) {
-        ret.add(singleValue.getIndex(), getDatatype().parseValue(singleValue.getWrapped()));
+        if (singleValue.getWrapped() == null) {
+          ret.add(singleValue.getIndex(), null);
+        } else {
+          ret.add(singleValue.getIndex(), getDatatype().parseValue(singleValue.getWrapped()));
+        }
       }
     } else {
       ret.add(getDatatype().parseValue(value));
diff --git a/src/main/java/caosdb/server/datatype/ReferenceValue.java b/src/main/java/caosdb/server/datatype/ReferenceValue.java
index 17fea765..25fa0b38 100644
--- a/src/main/java/caosdb/server/datatype/ReferenceValue.java
+++ b/src/main/java/caosdb/server/datatype/ReferenceValue.java
@@ -26,7 +26,6 @@ import caosdb.server.datatype.AbstractDatatype.Table;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.Message;
 import caosdb.server.utils.ServerMessages;
-import org.jdom2.Attribute;
 import org.jdom2.Element;
 
 public class ReferenceValue implements SingleValue {
@@ -122,11 +121,6 @@ public class ReferenceValue implements SingleValue {
     return toString();
   }
 
-  @Override
-  public void addToAttribute(final Attribute a) {
-    a.setValue(toString());
-  }
-
   @Override
   public boolean equals(final Object obj) {
     if (obj instanceof ReferenceValue) {
diff --git a/src/main/java/caosdb/server/datatype/SingleValue.java b/src/main/java/caosdb/server/datatype/SingleValue.java
index 60f370ed..2ae17a61 100644
--- a/src/main/java/caosdb/server/datatype/SingleValue.java
+++ b/src/main/java/caosdb/server/datatype/SingleValue.java
@@ -22,13 +22,9 @@
  */
 package caosdb.server.datatype;
 
-import org.jdom2.Attribute;
-
 public interface SingleValue extends Value {
 
   public AbstractDatatype.Table getTable();
 
   public String toDatabaseString();
-
-  public void addToAttribute(Attribute a);
 }
diff --git a/src/main/java/caosdb/server/datatype/TimespanDatatype.java b/src/main/java/caosdb/server/datatype/TimespanDatatype.java
index bdf83800..ef27eb67 100644
--- a/src/main/java/caosdb/server/datatype/TimespanDatatype.java
+++ b/src/main/java/caosdb/server/datatype/TimespanDatatype.java
@@ -1,148 +1,143 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package caosdb.server.datatype;
-
-import caosdb.server.datatype.AbstractDatatype.Table;
-import caosdb.server.entity.Message;
-import org.jdom2.Attribute;
-import org.jdom2.Element;
-
-@DatatypeDefinition(name = "Timespan")
-public class TimespanDatatype extends AbstractDatatype {
-
-  public static final Message TIMESPAN_NOT_PARSABLE = new Message(219, "Timespan not parsable.");
-
-  @Override
-  public Timespan parseValue(final Object value) throws Message {
-    Timespan ret = null;
-    try {
-      ret = Timespan.parse(value.toString());
-    } catch (final NumberFormatException e) {
-      // TODO define correct error message
-      throw TIMESPAN_NOT_PARSABLE;
-    }
-    return ret;
-  }
-}
-
-class Timespan implements SingleValue {
-  public enum Unit {
-    YEARS,
-    MONTHS,
-    WEEKS,
-    DAYS,
-    HOURS,
-    MINUTES,
-    SECONDS,
-    USERDEFINED
-  };
-
-  static Timespan parse(final String str) throws NumberFormatException {
-
-    final Timespan ts = new Timespan();
-    String[] parts;
-    if (str.matches("^\\s*[0-9]+(\\.[0-9]+)?([eE]-?[0-9]+)?\\s*[A-Za-z]+\\s*$")) {
-      parts = new String[2];
-      parts[0] = str.split("\\s*?[A-Za-z]*$")[0];
-      parts[1] = str.split("^[0-9]+(\\.[0-9]+)?([eE]-?[0-9]+)?\\s*")[1];
-
-      if (parts[1].equalsIgnoreCase(Unit.YEARS.toString()) || parts[1].equalsIgnoreCase("y")) {
-        ts.setUnit(Unit.YEARS);
-        ts.factor = 31556925.261;
-      } else if (parts[1].equalsIgnoreCase(Unit.MONTHS.toString()) || parts[1].equals("M")) {
-        ts.setUnit(Unit.MONTHS);
-        ts.factor = 31556925.261 / 12;
-      } else if (parts[1].equalsIgnoreCase(Unit.WEEKS.toString())
-          || parts[1].equalsIgnoreCase("w")) {
-        ts.setUnit(Unit.WEEKS);
-        ts.factor = 7 * 86400.0;
-      } else if (parts[1].equalsIgnoreCase(Unit.DAYS.toString())
-          || parts[1].equalsIgnoreCase("d")) {
-        ts.setUnit(Unit.DAYS);
-        ts.factor = 86400.0;
-      } else if (parts[1].equalsIgnoreCase(Unit.HOURS.toString())
-          || parts[1].equalsIgnoreCase("h")) {
-        ts.setUnit(Unit.HOURS);
-        ts.factor = 3600.0;
-      } else if (parts[1].equalsIgnoreCase(Unit.MINUTES.toString()) || parts[1].equals("m")) {
-        ts.setUnit(Unit.MINUTES);
-        ts.factor = 60.0;
-      } else if (parts[1].equalsIgnoreCase(Unit.SECONDS.toString())
-          || parts[1].equalsIgnoreCase("s")) {
-        ts.setUnit(Unit.SECONDS);
-      } else {
-        ts.setUnit(Unit.USERDEFINED);
-      }
-      ts.value = Double.parseDouble(parts[0]);
-    } else if (str.matches("^\\s*[0-9]+(\\.[0-9]+)?([eE]-?[0-9]+)?\\s*$")) {
-      ts.setUnit(null);
-      ts.value = Double.parseDouble(str);
-
-    } else {
-      throw new NumberFormatException("Couldn't parse String to Timespan.");
-    }
-    return ts;
-  }
-
-  private double value = 0;
-  private double factor = 1;
-
-  private Unit unit = null;
-
-  public double toSeconds() {
-    return value * factor;
-  }
-
-  @Override
-  public String toString() {
-    return Double.toString(value);
-  }
-
-  public String getUnit() {
-    return unit.toString();
-  }
-
-  private void setUnit(final Unit unit) {
-    this.unit = unit;
-  }
-
-  @Override
-  public void addToElement(Element e) {
-    e.addContent(toString());
-  }
-
-  @Override
-  public Table getTable() {
-    return Table.double_data;
-  }
-
-  @Override
-  public String toDatabaseString() {
-    return toString();
-  }
-
-  @Override
-  public void addToAttribute(Attribute a) {
-    a.setValue(toString());
-  }
-}
+/// *
+// * ** header v3.0
+// * This file is a part of the CaosDB Project.
+// *
+// * Copyright (C) 2018 Research Group Biomedical Physics,
+// * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+// *
+// * This program is free software: you can redistribute it and/or modify
+// * it under the terms of the GNU Affero General Public License as
+// * published by the Free Software Foundation, either version 3 of the
+// * License, or (at your option) any later version.
+// *
+// * This program is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// * GNU Affero General Public License for more details.
+// *
+// * You should have received a copy of the GNU Affero General Public License
+// * along with this program. If not, see <https://www.gnu.org/licenses/>.
+// *
+// * ** end header
+// */
+// package caosdb.server.datatype;
+//
+// import org.jdom2.Element;
+// import caosdb.server.datatype.AbstractDatatype.Table;
+// import caosdb.server.entity.Message;
+//
+// @DatatypeDefinition(name = "Timespan")
+// public class TimespanDatatype extends AbstractDatatype {
+//
+//  public static final Message TIMESPAN_NOT_PARSABLE = new Message(219, "Timespan not parsable.");
+//
+//  @Override
+//  public Timespan parseValue(final Object value) throws Message {
+//    Timespan ret = null;
+//    try {
+//      ret = Timespan.parse(value.toString());
+//    } catch (final NumberFormatException e) {
+//      // TODO define correct error message
+//      throw TIMESPAN_NOT_PARSABLE;
+//    }
+//    return ret;
+//  }
+// }
+//
+// class Timespan implements SingleValue {
+//  public enum Unit {
+//    YEARS,
+//    MONTHS,
+//    WEEKS,
+//    DAYS,
+//    HOURS,
+//    MINUTES,
+//    SECONDS,
+//    USERDEFINED
+//  };
+//
+//  static Timespan parse(final String str) throws NumberFormatException {
+//
+//    final Timespan ts = new Timespan();
+//    String[] parts;
+//    if (str.matches("^\\s*[0-9]+(\\.[0-9]+)?([eE]-?[0-9]+)?\\s*[A-Za-z]+\\s*$")) {
+//      parts = new String[2];
+//      parts[0] = str.split("\\s*?[A-Za-z]*$")[0];
+//      parts[1] = str.split("^[0-9]+(\\.[0-9]+)?([eE]-?[0-9]+)?\\s*")[1];
+//
+//      if (parts[1].equalsIgnoreCase(Unit.YEARS.toString()) || parts[1].equalsIgnoreCase("y")) {
+//        ts.setUnit(Unit.YEARS);
+//        ts.factor = 31556925.261;
+//      } else if (parts[1].equalsIgnoreCase(Unit.MONTHS.toString()) || parts[1].equals("M")) {
+//        ts.setUnit(Unit.MONTHS);
+//        ts.factor = 31556925.261 / 12;
+//      } else if (parts[1].equalsIgnoreCase(Unit.WEEKS.toString())
+//          || parts[1].equalsIgnoreCase("w")) {
+//        ts.setUnit(Unit.WEEKS);
+//        ts.factor = 7 * 86400.0;
+//      } else if (parts[1].equalsIgnoreCase(Unit.DAYS.toString())
+//          || parts[1].equalsIgnoreCase("d")) {
+//        ts.setUnit(Unit.DAYS);
+//        ts.factor = 86400.0;
+//      } else if (parts[1].equalsIgnoreCase(Unit.HOURS.toString())
+//          || parts[1].equalsIgnoreCase("h")) {
+//        ts.setUnit(Unit.HOURS);
+//        ts.factor = 3600.0;
+//      } else if (parts[1].equalsIgnoreCase(Unit.MINUTES.toString()) || parts[1].equals("m")) {
+//        ts.setUnit(Unit.MINUTES);
+//        ts.factor = 60.0;
+//      } else if (parts[1].equalsIgnoreCase(Unit.SECONDS.toString())
+//          || parts[1].equalsIgnoreCase("s")) {
+//        ts.setUnit(Unit.SECONDS);
+//      } else {
+//        ts.setUnit(Unit.USERDEFINED);
+//      }
+//      ts.value = Double.parseDouble(parts[0]);
+//    } else if (str.matches("^\\s*[0-9]+(\\.[0-9]+)?([eE]-?[0-9]+)?\\s*$")) {
+//      ts.setUnit(null);
+//      ts.value = Double.parseDouble(str);
+//
+//    } else {
+//      throw new NumberFormatException("Couldn't parse String to Timespan.");
+//    }
+//    return ts;
+//  }
+//
+//  private double value = 0;
+//  private double factor = 1;
+//
+//  private Unit unit = null;
+//
+//  public double toSeconds() {
+//    return value * factor;
+//  }
+//
+//  @Override
+//  public String toString() {
+//    return Double.toString(value);
+//  }
+//
+//  public String getUnit() {
+//    return unit.toString();
+//  }
+//
+//  private void setUnit(final Unit unit) {
+//    this.unit = unit;
+//  }
+//
+//  @Override
+//  public void addToElement(Element e) {
+//    e.addContent(toString());
+//  }
+//
+//  @Override
+//  public Table getTable() {
+//    return Table.double_data;
+//  }
+//
+//  @Override
+//  public String toDatabaseString() {
+//    return toString();
+//  }
+//
+// }
diff --git a/src/main/java/caosdb/server/entity/Entity.java b/src/main/java/caosdb/server/entity/Entity.java
index f9713af2..5c997d7f 100644
--- a/src/main/java/caosdb/server/entity/Entity.java
+++ b/src/main/java/caosdb/server/entity/Entity.java
@@ -794,15 +794,18 @@ public class Entity extends AbstractObservable implements EntityInterface {
     final CollectionValue vals = new CollectionValue();
     int pidx = 0;
     for (final Element pe : element.getChildren()) {
-      if (pe.getName().equalsIgnoreCase("Value")) {
+      if (pe.getName().equalsIgnoreCase("EmptyString")) {
+        // special case: empty string which cannot be distinguished from null
+        // values otherwise.
+        setValue(new GenericValue(""));
+      } else if (pe.getName().equalsIgnoreCase("Value")) {
         // Parse sub-elements which represent VALUES of this entity.
-
-        if (pe.getText() != null && pe.getTextTrim() != "") {
+        if (pe.getChild("EmptyString") != null) {
+          vals.add(new GenericValue(""));
+        } else if (pe.getText() != null && pe.getTextTrim() != "") {
           vals.add(new GenericValue(pe.getTextTrim()));
-        } else if (pe.getAttribute("null") != null) {
-          vals.add(null);
         } else {
-          vals.add(new GenericValue(""));
+          vals.add(null);
         }
       } else if (pe.getName().equalsIgnoreCase("Property")) {
         // Parse sub elements which represent PROPERTIES of this
@@ -866,8 +869,6 @@ public class Entity extends AbstractObservable implements EntityInterface {
     // Parse VALUE.
     if (vals.size() != 0) {
       setValue(vals);
-    } else if (element.getAttribute("null") != null) {
-      setValue(null);
     } else if (element.getTextTrim() != null && !element.getTextTrim().equals("")) {
       setValue(new GenericValue(element.getTextTrim()));
     }
diff --git a/src/main/java/caosdb/server/entity/xml/PropertyToElementStrategy.java b/src/main/java/caosdb/server/entity/xml/PropertyToElementStrategy.java
index 5fe0c0c5..a88b47cb 100644
--- a/src/main/java/caosdb/server/entity/xml/PropertyToElementStrategy.java
+++ b/src/main/java/caosdb/server/entity/xml/PropertyToElementStrategy.java
@@ -47,9 +47,8 @@ public class PropertyToElementStrategy extends EntityToElementStrategy {
           switch (MagicTypes.getType(entity.getId())) {
             case UNIT:
               if (setFieldStrategy.isToBeSet("unit") && v != null) {
-                final Attribute a = new Attribute("unit", "");
+                final Attribute a = new Attribute("unit", ((SingleValue) v).toDatabaseString());
                 element.setAttribute(a);
-                ((SingleValue) v).addToAttribute(a);
               }
               return element;
             default:
-- 
GitLab