Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions cw1/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>ru.spbau.group202.sharkova.cw1</groupId>
<artifactId>cw1</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<version>1.16.0</version>
<scope>test</scope>
</dependency>
<!--
https://mvnrepository.com/artifact/com.intellij/annotations
-->
<dependency>
<groupId>com.intellij</groupId>
<artifactId>annotations</artifactId>
<version>12.0</version>
</dependency>


<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

<build>
<plugins>
<plugin>
<groupId>com.zenjava</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>8.8.3</version>
<configuration>
<mainClass>your.package.with.Launcher</mainClass>
</configuration>
</plugin>
</plugins>
</build>


</project>
22 changes: 22 additions & 0 deletions cw1/src/main/java/ru/spbau/group202/cw1/MD5Hasher.java
Original file line number Diff line number Diff line change
@@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

исключения тоже бы хорошо документировать

*/
@NotNull
public abstract byte[] getHashFromPath(@NotNull Path path) throws IOException, NoSuchAlgorithmException;
}
85 changes: 85 additions & 0 deletions cw1/src/main/java/ru/spbau/group202/cw1/MD5MultiThread.java
Original file line number Diff line number Diff line change
@@ -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<RecursiveTask<byte[]>> tasks = new ArrayList<>();
for (Path p : Files.walk(path).filter(Files::isRegularFile).collect(Collectors.toList())) {
RecursiveTask<byte[]> task = new RecursiveTask<byte[]>() {
@Override
protected byte[] compute() {
try {
return getHashFromPath(p);
} catch (Exception e) {
// TODO handle exceptions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ага, todo :)

}
return null;
}
};
tasks.add(task);
pool.execute(task);
}

for (RecursiveTask<byte[]> 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) {
;
}
}
}


}
42 changes: 42 additions & 0 deletions cw1/src/main/java/ru/spbau/group202/cw1/MD5SingleThread.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
40 changes: 40 additions & 0 deletions cw1/src/main/java/ru/spbau/group202/cw1/Main.java
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
}
}
100 changes: 100 additions & 0 deletions cw1/src/test/java/ru/spbau/group202/cw1/MD5HasherTest.java
Original file line number Diff line number Diff line change
@@ -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!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
}
1 change: 1 addition & 0 deletions cw1/src/test/resources/testdir/file1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
filefile
Empty file.