diff --git a/commons/src/main/java/lcsb/mapviewer/common/Comparator.java b/commons/src/main/java/lcsb/mapviewer/common/Comparator.java
index b096d920c8e7605a246ae94b07dc2476656d7268..772b161ba756b8795d14238ff88e054c4946f710 100644
--- a/commons/src/main/java/lcsb/mapviewer/common/Comparator.java
+++ b/commons/src/main/java/lcsb/mapviewer/common/Comparator.java
@@ -1,21 +1,18 @@
 package lcsb.mapviewer.common;
 
-import java.util.ArrayList;
-import java.util.List;
-
+import lcsb.mapviewer.common.exception.NotImplementedException;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import lcsb.mapviewer.common.exception.NotImplementedException;
+import java.util.ArrayList;
+import java.util.List;
 
 public abstract class Comparator<T extends Object> implements java.util.Comparator<T> {
-  /**
-   * Default class logger.
-   */
-  private static Logger logger = LogManager.getLogger();
-  private Class<T> comparatorClazz;
-  private boolean exactClassMatch;
-  private List<Comparator<? extends T>> subClassComparatorList = new ArrayList<>();
+  protected static Logger logger = LogManager.getLogger();
+  
+  private final Class<T> comparatorClazz;
+  private final boolean exactClassMatch;
+  private final List<Comparator<? extends T>> subClassComparatorList = new ArrayList<>();
 
   protected Comparator(final Class<T> clazz) {
     this(clazz, false);
@@ -26,7 +23,7 @@ public abstract class Comparator<T extends Object> implements java.util.Comparat
     this.exactClassMatch = exactClassMatch;
   }
 
-  @SuppressWarnings({ "rawtypes", "unchecked" })
+  @SuppressWarnings({"rawtypes", "unchecked"})
   @Override
   public final int compare(final T arg0, final T arg1) {
     if (arg0 == null) {
@@ -68,7 +65,7 @@ public abstract class Comparator<T extends Object> implements java.util.Comparat
     return null;
   }
 
-  @SuppressWarnings({ "rawtypes", "unchecked" })
+  @SuppressWarnings({"rawtypes", "unchecked"})
   private int compareParents(final T arg0, final T arg1) {
     Comparator parentComparator = getParentComparator();
     while (parentComparator != null) {
diff --git a/converter-CellDesigner/src/test/java/lcsb/mapviewer/converter/model/celldesigner/CellDesignerTestFunctions.java b/converter-CellDesigner/src/test/java/lcsb/mapviewer/converter/model/celldesigner/CellDesignerTestFunctions.java
index 615388da0952036d46207f63def095e6c9c4fa9a..ca10f5af928fe864949ea77230b733cbb5f78a3e 100644
--- a/converter-CellDesigner/src/test/java/lcsb/mapviewer/converter/model/celldesigner/CellDesignerTestFunctions.java
+++ b/converter-CellDesigner/src/test/java/lcsb/mapviewer/converter/model/celldesigner/CellDesignerTestFunctions.java
@@ -6,6 +6,7 @@ import lcsb.mapviewer.common.tests.UnitTestFailedWatcher;
 import lcsb.mapviewer.converter.ConverterParams;
 import lcsb.mapviewer.converter.InvalidInputDataExecption;
 import lcsb.mapviewer.converter.model.celldesigner.structure.CellDesignerSpecies;
+import lcsb.mapviewer.model.Project;
 import lcsb.mapviewer.model.graphics.HorizontalAlign;
 import lcsb.mapviewer.model.graphics.VerticalAlign;
 import lcsb.mapviewer.model.map.Drawable;
@@ -283,4 +284,11 @@ public abstract class CellDesignerTestFunctions extends TestUtils {
     result.setZ(faker.number().numberBetween(1, 100));
     return result;
   }
+
+  protected Project createProject() {
+    Project project = new Project();
+    project.setProjectId(faker.numerify("P########"));
+    return project;
+  }
+
 }
diff --git a/converter-CellDesigner/src/test/java/lcsb/mapviewer/converter/model/celldesigner/ProjectExportTest.java b/converter-CellDesigner/src/test/java/lcsb/mapviewer/converter/model/celldesigner/ProjectExportTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e2a0795446b48d934328545373d3836bca356ce5
--- /dev/null
+++ b/converter-CellDesigner/src/test/java/lcsb/mapviewer/converter/model/celldesigner/ProjectExportTest.java
@@ -0,0 +1,79 @@
+package lcsb.mapviewer.converter.model.celldesigner;
+
+import lcsb.mapviewer.converter.ComplexZipConverter;
+import lcsb.mapviewer.converter.ComplexZipConverterParams;
+import lcsb.mapviewer.converter.ProjectFactory;
+import lcsb.mapviewer.converter.zip.ZipEntryFile;
+import lcsb.mapviewer.converter.zip.ZipEntryFileFactory;
+import lcsb.mapviewer.model.Project;
+import lcsb.mapviewer.model.ProjectComparator;
+import lcsb.mapviewer.model.map.model.Model;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import static org.junit.Assert.assertEquals;
+
+public class ProjectExportTest extends CellDesignerTestFunctions {
+
+  private final ProjectComparator projectComparator = new ProjectComparator();
+
+  private final ZipEntryFileFactory zefFactory = new ZipEntryFileFactory();
+
+  @Before
+  public void setUp() throws Exception {
+  }
+
+  @After
+  public void tearDown() throws Exception {
+  }
+
+  @Test
+  public void testBasic() throws Exception {
+    Project project = createProject();
+    Model model = createEmptyModel();
+    project.addModel(model);
+
+    final ComplexZipConverter parser = new ComplexZipConverter(CellDesignerXmlParser.class);
+    final ProjectFactory projectFactory = new ProjectFactory(parser);
+
+    byte[] data = projectFactory.project2zip(project);
+
+    File tempFile = File.createTempFile("CD-", ".zip");
+
+    try (FileOutputStream outputStream = new FileOutputStream(tempFile)) {
+      outputStream.write(data);
+    }
+    logger.debug(tempFile.getAbsolutePath());
+
+    final ComplexZipConverterParams complexParams = createDefaultParams(tempFile);
+
+    Project project2 = projectFactory.create(complexParams);
+
+    assertEquals(0, projectComparator.compare(project, project2));
+  }
+
+  private ComplexZipConverterParams createDefaultParams(final File tempFile) throws IOException {
+    final ComplexZipConverterParams complexParams;
+    complexParams = new ComplexZipConverterParams().zipFile(new ZipFile(tempFile));
+    complexParams.visualizationDir(faker.name().username());
+
+    ZipFile zipFile = new ZipFile(tempFile.getAbsolutePath());
+    Enumeration<? extends ZipEntry> entries = zipFile.entries();
+    while (entries.hasMoreElements()) {
+      ZipEntry entry = entries.nextElement();
+      ZipEntryFile e = zefFactory.createZipEntryFile(entry, zipFile);
+      complexParams.entry(e);
+    }
+    zipFile.close();
+    return complexParams;
+  }
+
+}
diff --git a/converter/src/main/java/lcsb/mapviewer/converter/ComplexZipConverter.java b/converter/src/main/java/lcsb/mapviewer/converter/ComplexZipConverter.java
index 3c320b72e1dc3aba3ba6ee53c0605dd595d237a1..bea7b0376e89ae1e8ebf0502f8ce3f6629bfcadc 100644
--- a/converter/src/main/java/lcsb/mapviewer/converter/ComplexZipConverter.java
+++ b/converter/src/main/java/lcsb/mapviewer/converter/ComplexZipConverter.java
@@ -427,4 +427,5 @@ public class ComplexZipConverter {
     }
     return converter;
   }
+
 }
diff --git a/converter/src/main/java/lcsb/mapviewer/converter/ProjectFactory.java b/converter/src/main/java/lcsb/mapviewer/converter/ProjectFactory.java
index e8af1d4bac741185e0e7f8cd943cef003f1d6d9b..fe2b69e772b00b0bf6d4d881fa9d6ac0d210a5e2 100644
--- a/converter/src/main/java/lcsb/mapviewer/converter/ProjectFactory.java
+++ b/converter/src/main/java/lcsb/mapviewer/converter/ProjectFactory.java
@@ -1,23 +1,12 @@
 package lcsb.mapviewer.converter;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
 import lcsb.mapviewer.common.exception.InvalidArgumentException;
 import lcsb.mapviewer.converter.zip.GlyphZipEntryFile;
 import lcsb.mapviewer.converter.zip.ImageZipEntryFile;
 import lcsb.mapviewer.converter.zip.LayoutZipEntryFile;
 import lcsb.mapviewer.converter.zip.ZipEntryFile;
 import lcsb.mapviewer.model.Project;
+import lcsb.mapviewer.model.map.InconsistentModelException;
 import lcsb.mapviewer.model.map.layout.graphics.Glyph;
 import lcsb.mapviewer.model.map.layout.graphics.Layer;
 import lcsb.mapviewer.model.map.layout.graphics.LayerText;
@@ -25,6 +14,19 @@ import lcsb.mapviewer.model.map.model.Model;
 import lcsb.mapviewer.model.map.model.ModelData;
 import lcsb.mapviewer.model.map.model.ModelSubmodelConnection;
 import lcsb.mapviewer.model.map.species.Element;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
 
 public class ProjectFactory {
 
@@ -32,11 +34,11 @@ public class ProjectFactory {
    * Default class logger.
    */
   @SuppressWarnings("unused")
-  private Logger logger = LogManager.getLogger();
+  private final Logger logger = LogManager.getLogger();
 
-  private ComplexZipConverter converter;
+  private final ComplexZipConverter converter;
 
-  private GlyphParser glyphParser = new GlyphParser();
+  private final GlyphParser glyphParser = new GlyphParser();
 
   public ProjectFactory(final ComplexZipConverter converter) {
     this.converter = converter;
@@ -80,12 +82,12 @@ public class ProjectFactory {
           }
         }
       }
-      if (imageEntries.size() > 0) {
+      if (!imageEntries.isEmpty()) {
         OverviewParser parser = new OverviewParser();
         project
             .addOverviewImages(parser.parseOverviewLinks(models, imageEntries, params.getVisualizationDir(), zipFile));
       }
-      if (project.getGlyphs().size() > 0) {
+      if (!project.getGlyphs().isEmpty()) {
         assignGlyphsToElements(project);
       }
       return project;
@@ -97,8 +99,7 @@ public class ProjectFactory {
   }
 
   private void assignGlyphsToElements(final Project project) throws InvalidGlyphFile {
-    Set<ModelData> models = new HashSet<>();
-    models.addAll(project.getModels());
+    Set<ModelData> models = new HashSet<>(project.getModels());
 
     for (final ModelData model : project.getModels()) {
       for (final ModelSubmodelConnection connection : model.getSubmodels()) {
@@ -127,10 +128,10 @@ public class ProjectFactory {
 
   String removeGlyph(final String notes) {
     String[] lines = notes.split("[\n\r]+");
-    StringBuilder result = new StringBuilder("");
+    StringBuilder result = new StringBuilder();
     for (final String line : lines) {
       if (!line.startsWith("Glyph:")) {
-        result.append(line + "\n");
+        result.append(line).append("\n");
       }
     }
     return result.toString();
@@ -152,4 +153,20 @@ public class ProjectFactory {
     return null;
   }
 
+  public byte[] project2zip(final Project project) throws ConverterException, InconsistentModelException {
+    Converter converter = this.converter.createConverterInstance();
+    String topModelContent = converter.model2String(project.getTopModel());
+
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    try (ZipOutputStream zos = new ZipOutputStream(byteArrayOutputStream)) {
+      ZipEntry entry = new ZipEntry(project.getTopModel().getName() + "." + converter.getFileExtensions().get(0));
+
+      zos.putNextEntry(entry);
+      zos.write(topModelContent.getBytes());
+      zos.closeEntry();
+    } catch (IOException ioe) {
+      throw new ConverterException(ioe);
+    }
+    return byteArrayOutputStream.toByteArray();
+  }
 }
diff --git a/model/src/main/java/lcsb/mapviewer/model/ProjectComparator.java b/model/src/main/java/lcsb/mapviewer/model/ProjectComparator.java
new file mode 100644
index 0000000000000000000000000000000000000000..0aa66945003d38504dbd9ecf90e95b08c8b15cf2
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/ProjectComparator.java
@@ -0,0 +1,43 @@
+package lcsb.mapviewer.model;
+
+import lcsb.mapviewer.common.Comparator;
+import lcsb.mapviewer.common.Configuration;
+import lcsb.mapviewer.common.comparator.SetComparator;
+import lcsb.mapviewer.model.map.model.ModelComparator;
+import lcsb.mapviewer.model.map.model.ModelData;
+import lcsb.mapviewer.model.map.model.ModelDataComparator;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class ProjectComparator extends Comparator<Project> {
+
+  private static final Logger logger = LogManager.getLogger();
+
+  private final ModelComparator modelComparator;
+  private final SetComparator<ModelData> modelDataSetComparator;
+
+  public ProjectComparator(final double epsilon) {
+    super(Project.class);
+    modelComparator = new ModelComparator(epsilon);
+    modelDataSetComparator = new SetComparator<>(new ModelDataComparator(epsilon));
+  }
+
+  public ProjectComparator() {
+    this(Configuration.EPSILON);
+  }
+
+  @Override
+  protected int internalCompare(final Project arg0, final Project arg1) {
+    if (modelComparator.compare(arg0.getTopModel(), arg1.getTopModel()) != 0) {
+      logger.debug("Top model different: {}, {}", arg0.getTopModel(), arg1.getTopModel());
+      return modelComparator.compare(arg0.getTopModel(), arg1.getTopModel());
+    }
+
+    if (modelDataSetComparator.compare(arg0.getModels(), arg1.getModels()) != 0) {
+      logger.debug("Models different");
+      return modelDataSetComparator.compare(arg0.getModels(), arg1.getModels());
+    }
+    return 0;
+  }
+
+}
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/model/ModelComparator.java b/model/src/main/java/lcsb/mapviewer/model/map/model/ModelComparator.java
index 16f6065ca420fe5e99e1da9463be5cddcb51962b..3b56cb07999024878e592de0c4a98248d10fafc0 100644
--- a/model/src/main/java/lcsb/mapviewer/model/map/model/ModelComparator.java
+++ b/model/src/main/java/lcsb/mapviewer/model/map/model/ModelComparator.java
@@ -1,14 +1,5 @@
 package lcsb.mapviewer.model.map.model;
 
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
 import lcsb.mapviewer.common.Comparator;
 import lcsb.mapviewer.common.Configuration;
 import lcsb.mapviewer.common.comparator.CalendarComparator;
@@ -33,22 +24,23 @@ import lcsb.mapviewer.model.map.species.Element;
 import lcsb.mapviewer.model.map.species.ElementComparator;
 import lcsb.mapviewer.modelutils.map.ElementUtils;
 
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
 /**
  * Comparator for {@link Model} class.
- * 
+ *
  * @author Piotr Gawron
- * 
  */
 public class ModelComparator extends Comparator<Model> {
-  /**
-   * Default class logger.
-   */
-  private static Logger logger = LogManager.getLogger();
 
   /**
    * Epsilon value used for comparison of doubles.
    */
-  private double epsilon;
+  private final double epsilon;
 
   private SetComparator<SbmlUnit> unitSetComparator = new SetComparator<>(new SbmlUnitComparator());
 
@@ -56,13 +48,12 @@ public class ModelComparator extends Comparator<Model> {
 
   private ElementComparator elementComparator;
 
-  private ElementUtils eu = new ElementUtils();
+  private final ElementUtils eu = new ElementUtils();
 
   /**
    * Constructor that requires {@link #epsilon} parameter.
-   * 
-   * @param epsilon
-   *          {@link #epsilon}
+   *
+   * @param epsilon {@link #epsilon}
    */
   public ModelComparator(final double epsilon) {
     super(Model.class);
@@ -125,7 +116,7 @@ public class ModelComparator extends Comparator<Model> {
       return status;
     }
 
-    SetComparator<Layer> layerSetComparator = new SetComparator<Layer>(new LayerComparator(epsilon)); 
+    SetComparator<Layer> layerSetComparator = new SetComparator<Layer>(new LayerComparator(epsilon));
 
     status = layerSetComparator.compare(arg0.getLayers(), arg1.getLayers());
     if (status != 0) {
@@ -202,13 +193,10 @@ public class ModelComparator extends Comparator<Model> {
 
   /**
    * Compares two sets of elements.
-   * 
-   * @param elements
-   *          first set of elements
-   * @param elements2
-   *          second set of elements
-   * @return if sets are equal then returns 0. If they are different then -1/1
-   *         is returned.
+   *
+   * @param elements  first set of elements
+   * @param elements2 second set of elements
+   * @return if sets are equal then returns 0. If they are different then -1/1 is returned.
    */
   private int compareElements(final Set<Element> elements, final Set<Element> elements2) {
 
@@ -216,8 +204,8 @@ public class ModelComparator extends Comparator<Model> {
     Map<String, Element> map2 = new HashMap<>();
 
     if (elements.size() != elements2.size()) {
-      logger.debug("Number of elements different: " + elements.size() + ", " + elements2.size());
-      return ((Integer) elements.size()).compareTo(elements2.size());
+      logger.debug("Number of elements different: {}, {}", elements.size(), elements2.size());
+      return Integer.compare(elements.size(), elements2.size());
     }
 
     for (final Element element : elements) {
@@ -248,15 +236,13 @@ public class ModelComparator extends Comparator<Model> {
 
   /**
    * Compares two collection of models.
-   * 
-   * @param collection1
-   *          first collection to compare
-   * @param collection2
-   *          second collection to compare
+   *
+   * @param collection1 first collection to compare
+   * @param collection2 second collection to compare
    * @return 0 if the collections are identical, -1/1 otherwise
    */
   private int compareSubmodels(final Collection<ModelSubmodelConnection> collection1,
-      final Collection<ModelSubmodelConnection> collection2) {
+                               final Collection<ModelSubmodelConnection> collection2) {
     IntegerComparator integerComparator = new IntegerComparator();
     if (integerComparator.compare(collection1.size(), collection2.size()) != 0) {
       logger.debug("collection of submodels doesn't match: " + collection1.size() + ", " + collection2.size());
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/model/ModelDataComparator.java b/model/src/main/java/lcsb/mapviewer/model/map/model/ModelDataComparator.java
new file mode 100644
index 0000000000000000000000000000000000000000..21ca1415b96e2c351b1a9cf14ddfdf45374c3188
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/model/ModelDataComparator.java
@@ -0,0 +1,26 @@
+package lcsb.mapviewer.model.map.model;
+
+import lcsb.mapviewer.common.Comparator;
+import lcsb.mapviewer.common.Configuration;
+
+public class ModelDataComparator extends Comparator<ModelData> {
+
+  private final ModelComparator modelComparator;
+
+  public ModelDataComparator(final double epsilon) {
+    super(ModelData.class);
+    modelComparator = new ModelComparator(epsilon);
+  }
+
+  /**
+   * Default constructor.
+   */
+  public ModelDataComparator() {
+    this(Configuration.EPSILON);
+  }
+
+  @Override
+  protected int internalCompare(final ModelData arg0, final ModelData arg1) {
+    return modelComparator.compare(new ModelFullIndexed(arg0), new ModelFullIndexed(arg1));
+  }
+}
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/reaction/AbstractNodeComparator.java b/model/src/main/java/lcsb/mapviewer/model/map/reaction/AbstractNodeComparator.java
index c84733e90ae90e6cbf1d43fd4463275c85fd191b..68a975713ffec544e13837667119da71fc6ba096 100644
--- a/model/src/main/java/lcsb/mapviewer/model/map/reaction/AbstractNodeComparator.java
+++ b/model/src/main/java/lcsb/mapviewer/model/map/reaction/AbstractNodeComparator.java
@@ -1,8 +1,5 @@
 package lcsb.mapviewer.model.map.reaction;
 
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
 import lcsb.mapviewer.common.Comparator;
 import lcsb.mapviewer.common.Configuration;
 import lcsb.mapviewer.model.graphics.PolylineDataComparator;
@@ -10,28 +7,22 @@ import lcsb.mapviewer.model.graphics.PolylineDataComparator;
 /**
  * This class implements comparator interface for AbstractNode. It also handles
  * comparison of subclasses of AbstractNode class.
- * 
+ *
  * @author Piotr Gawron
- * 
  */
 public class AbstractNodeComparator extends Comparator<AbstractNode> {
-  /**
-   * Default class logger.
-   */
-  private static Logger logger = LogManager.getLogger();
 
   /**
    * Epsilon value used for comparison of doubles.
    */
-  private double epsilon;
+  private final double epsilon;
 
-  private boolean ignoreLayout;
+  private final boolean ignoreLayout;
 
   /**
    * Constructor that requires {@link #epsilon} parameter.
-   * 
-   * @param epsilon
-   *          {@link #epsilon}
+   *
+   * @param epsilon {@link #epsilon}
    */
   public AbstractNodeComparator(final double epsilon, final boolean ignoreLayout) {
     super(AbstractNode.class, true);