diff --git a/cw1/pom.xml b/cw1/pom.xml
new file mode 100644
index 0000000..b0bf10b
--- /dev/null
+++ b/cw1/pom.xml
@@ -0,0 +1,62 @@
+
+
+ 4.0.0
+
+ ru.spbau.group202.sharkova.cw1
+ cw1
+ 1.0-SNAPSHOT
+
+
+
+
+ junit
+ junit
+ 4.9
+ test
+
+
+ com.github.stefanbirkner
+ system-rules
+ 1.16.0
+ test
+
+
+
+ com.intellij
+ annotations
+ 12.0
+
+
+
+
+
+ org.mockito
+ mockito-all
+ 1.10.19
+ test
+
+
+
+ 1.8
+ 1.8
+
+
+
+
+
+ com.zenjava
+ javafx-maven-plugin
+ 8.8.3
+
+ your.package.with.Launcher
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cw1/src/main/java/ru/spbau/group202/cw1/MD5Hasher.java b/cw1/src/main/java/ru/spbau/group202/cw1/MD5Hasher.java
new file mode 100644
index 0000000..a25abbe
--- /dev/null
+++ b/cw1/src/main/java/ru/spbau/group202/cw1/MD5Hasher.java
@@ -0,0 +1,22 @@
+package ru.spbau.group202.cw1;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Instances of this abstract class calculate MD5 check-sum of a directory.
+ */
+abstract public class MD5Hasher {
+ protected final static int BUFFER_SIZE = 4096;
+
+ /**
+ * This method calculates MD5 check-sum of a file or a directory.
+ * @param path path to file/directory
+ * @return byte[] array storing MD5 check-sum
+ */
+ @NotNull
+ public abstract byte[] getHashFromPath(@NotNull Path path) throws IOException, NoSuchAlgorithmException;
+}
diff --git a/cw1/src/main/java/ru/spbau/group202/cw1/MD5MultiThread.java b/cw1/src/main/java/ru/spbau/group202/cw1/MD5MultiThread.java
new file mode 100644
index 0000000..44f34dc
--- /dev/null
+++ b/cw1/src/main/java/ru/spbau/group202/cw1/MD5MultiThread.java
@@ -0,0 +1,85 @@
+package ru.spbau.group202.cw1;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.RecursiveTask;
+import java.util.stream.Collectors;
+
+/**
+ * This implementation of MD5Hasher abstract class uses a ForkJoinPool
+ * to compute MD5 check-sum.
+ */
+public class MD5MultiThread extends MD5Hasher {
+
+ private ForkJoinPool pool;
+
+ public MD5MultiThread(int numberOfThreads) {
+ pool = new ForkJoinPool(numberOfThreads);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @NotNull
+ public byte[] getHashFromPath(@NotNull Path path) throws IOException, NoSuchAlgorithmException {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ if (Files.isDirectory(path)) {
+ processDirectory(path, md5);
+ } else {
+ processFile(path, md5);
+ }
+
+ return md5.digest();
+ }
+
+ private void processDirectory(@NotNull Path path, @NotNull MessageDigest md) throws IOException {
+ md.update(path.getFileName().toString().getBytes());
+ ArrayList> tasks = new ArrayList<>();
+ for (Path p : Files.walk(path).filter(Files::isRegularFile).collect(Collectors.toList())) {
+ RecursiveTask task = new RecursiveTask() {
+ @Override
+ protected byte[] compute() {
+ try {
+ return getHashFromPath(p);
+ } catch (Exception e) {
+ // TODO handle exceptions
+ }
+ return null;
+ }
+ };
+ tasks.add(task);
+ pool.execute(task);
+ }
+
+ for (RecursiveTask task : tasks) {
+ try {
+ byte[] hash = task.get();
+ if (hash == null) {
+ throw new IOException("Unable to compute");
+ }
+ md.update(hash);
+ } catch (Exception e) {
+ throw new IOException("ForkJoinPool exception; unable to compute.");
+ }
+ }
+ }
+
+ private void processFile(@NotNull Path path, @NotNull MessageDigest md) throws IOException {
+ try (DigestInputStream stream = new DigestInputStream(Files.newInputStream(path), md)) {
+ byte[] buf = new byte[BUFFER_SIZE];
+ while (stream.read(buf) != -1) {
+ ;
+ }
+ }
+ }
+
+
+}
diff --git a/cw1/src/main/java/ru/spbau/group202/cw1/MD5SingleThread.java b/cw1/src/main/java/ru/spbau/group202/cw1/MD5SingleThread.java
new file mode 100644
index 0000000..8241680
--- /dev/null
+++ b/cw1/src/main/java/ru/spbau/group202/cw1/MD5SingleThread.java
@@ -0,0 +1,42 @@
+package ru.spbau.group202.cw1;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.stream.Collectors;
+
+/**
+ * This implementation of MD5Hasher abstract class calculates MD5 check-sum
+ * in a regular way, using a single thread.
+ */
+public class MD5SingleThread extends MD5Hasher {
+
+ /**
+ * {@inheritDoc}
+ */
+ @NotNull
+ public byte[] getHashFromPath(@NotNull Path path) throws IOException, NoSuchAlgorithmException {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ if (Files.isDirectory(path)) {
+ md5.update(path.getFileName().toString().getBytes());
+ for (Path p : Files.walk(path).filter(Files::isRegularFile).collect(Collectors.toList())) {
+ md5.update(getHashFromPath(p));
+ }
+
+ return md5.digest();
+ }
+
+ try (DigestInputStream stream = new DigestInputStream(Files.newInputStream(path), md5)) {
+ byte[] buf = new byte[BUFFER_SIZE];
+ while (stream.read(buf) != -1) {
+ ;
+ }
+ return stream.getMessageDigest().digest();
+ }
+ }
+}
diff --git a/cw1/src/main/java/ru/spbau/group202/cw1/Main.java b/cw1/src/main/java/ru/spbau/group202/cw1/Main.java
new file mode 100644
index 0000000..460eaf3
--- /dev/null
+++ b/cw1/src/main/java/ru/spbau/group202/cw1/Main.java
@@ -0,0 +1,40 @@
+package ru.spbau.group202.cw1;
+
+import javax.xml.bind.DatatypeConverter;
+import java.nio.file.Paths;
+
+public class Main {
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.out.println("Incorrect usage; specify filepaths.");
+ return;
+ }
+
+ MD5MultiThread concurrentHasher = new MD5MultiThread(4);
+ MD5SingleThread regularHasher = new MD5SingleThread();
+ for (String path : args) {
+ System.out.println(path + ":");
+ try {
+ long start = System.currentTimeMillis();
+ byte[] res = regularHasher.getHashFromPath(Paths.get(path));
+ System.out.println(DatatypeConverter.printHexBinary(res));
+ long diff1 = System.currentTimeMillis() - start;
+ System.out.println("Single-thread computation: " + diff1 + " ms.");
+
+ start = System.currentTimeMillis();
+ res = concurrentHasher.getHashFromPath(Paths.get(path));
+ System.out.println(DatatypeConverter.printHexBinary(res));
+ long diff2 = System.currentTimeMillis() - start;
+ System.out.println("Multi-thread computation: " + diff2 + " ms.");
+
+ System.out.println("\n The difference is " + (diff1 - diff2) + " ms.\n");
+ } catch (Exception e) {
+ if (e.getMessage() != null) {
+ System.out.println(e.getMessage());
+ } else {
+ System.out.println("Unable to compute md5 of this file");
+ }
+ }
+ }
+ }
+}
diff --git a/cw1/src/test/java/ru/spbau/group202/cw1/MD5HasherTest.java b/cw1/src/test/java/ru/spbau/group202/cw1/MD5HasherTest.java
new file mode 100644
index 0000000..b84f6d9
--- /dev/null
+++ b/cw1/src/test/java/ru/spbau/group202/cw1/MD5HasherTest.java
@@ -0,0 +1,100 @@
+package ru.spbau.group202.cw1;
+
+import org.junit.Test;
+
+import javax.xml.bind.DatatypeConverter;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.NoSuchAlgorithmException;
+
+import static org.junit.Assert.*;
+
+// IMPORTANT: testEmptyDir will only work if you create 'dir' directory manually
+// git doesn't allow empty directories to be committed
+
+/**
+ * This class tests correctness of MD5Hasher implementations methods
+ */
+public class MD5HasherTest {
+
+ private final static int numberOfThreads = 4;
+
+ // create 'dir' directory manually!
+ private void testEmptyDir(MD5Hasher hasher) throws NoSuchAlgorithmException, IOException {
+ File file = new File("src/test/resources/testdir/dir");
+ Path path = file.toPath();
+ byte[] hash = hasher.getHashFromPath(path);
+ String str = DatatypeConverter.printHexBinary(hash);
+ // this md5 checksum was calculated here: http://progs.be/md5.html
+ assertEquals("736007832d2167baaae763fd3a3f3cf1", str.toLowerCase());
+ }
+
+ private void testEmptyFile(MD5Hasher hasher) throws NoSuchAlgorithmException, IOException {
+ File file = new File("src/test/resources/testdir/file2.txt");
+ Path path = file.toPath();
+ byte[] hash = hasher.getHashFromPath(path);
+ String str = DatatypeConverter.printHexBinary(hash);
+ // this md5 checksum was calculated here: http://onlinemd5.com/
+ assertEquals("D41D8CD98F00B204E9800998ECF8427E", str);
+ }
+
+ private void testNonEmptyFile(MD5Hasher hasher) throws NoSuchAlgorithmException, IOException {
+ File file = new File("src/test/resources/testdir/file1.txt");
+ Path path = file.toPath();
+ byte[] hash = hasher.getHashFromPath(path);
+ String str = DatatypeConverter.printHexBinary(hash);
+ // this md5 checksum was calculated here: http://onlinemd5.com/
+ assertEquals("40A5D58FFA6E88AA578D6683AC413105", str);
+ }
+
+ @Test
+ public void testEmptyDirRegularHasher() throws NoSuchAlgorithmException, IOException {
+ MD5SingleThread hasher = new MD5SingleThread();
+ testEmptyDir(hasher);
+ }
+
+ @Test
+ public void testEmptyDirConcurrentHasher() throws NoSuchAlgorithmException, IOException {
+ MD5MultiThread hasher = new MD5MultiThread(numberOfThreads);
+ testEmptyDir(hasher);
+ }
+
+ @Test
+ public void testEmptyFileRegularHasher() throws NoSuchAlgorithmException, IOException {
+ MD5SingleThread hasher = new MD5SingleThread();
+ testEmptyFile(hasher);
+ }
+
+ @Test
+ public void testEmptyFileConcurrentHasher() throws NoSuchAlgorithmException, IOException {
+ MD5MultiThread hasher = new MD5MultiThread(numberOfThreads);
+ testEmptyFile(hasher);
+ }
+
+ @Test
+ public void testNonEmptyFileRegularHasher() throws NoSuchAlgorithmException, IOException {
+ MD5SingleThread hasher = new MD5SingleThread();
+ testNonEmptyFile(hasher);
+ }
+
+ @Test
+ public void testNonEmptyFileConcurrentHasher() throws NoSuchAlgorithmException, IOException {
+ MD5MultiThread hasher = new MD5MultiThread(numberOfThreads);
+ testNonEmptyFile(hasher);
+ }
+
+ @Test
+ public void testEqualResultsSingleAndMultiThread() throws NoSuchAlgorithmException, IOException {
+ MD5SingleThread regularHasher = new MD5SingleThread();
+ MD5MultiThread concurrentHasher = new MD5MultiThread(numberOfThreads);
+
+ File file = new File("src/test/resources/testdir");
+ Path path = file.toPath();
+ byte[] hash = regularHasher.getHashFromPath(path);
+ String str1 = DatatypeConverter.printHexBinary(hash);
+ hash = concurrentHasher.getHashFromPath(path);
+ String str2 = DatatypeConverter.printHexBinary(hash);
+ assertEquals(str1, str2);
+ }
+}
\ No newline at end of file
diff --git a/cw1/src/test/resources/testdir/file1.txt b/cw1/src/test/resources/testdir/file1.txt
new file mode 100644
index 0000000..3881968
--- /dev/null
+++ b/cw1/src/test/resources/testdir/file1.txt
@@ -0,0 +1 @@
+filefile
\ No newline at end of file
diff --git a/cw1/src/test/resources/testdir/file2.txt b/cw1/src/test/resources/testdir/file2.txt
new file mode 100644
index 0000000..e69de29