diff --git a/part_4/exercise_4/.gitignore b/part_4/exercise_4/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4dc3c31488d72e7da5e33ad0bee70a46afd7f69f --- /dev/null +++ b/part_4/exercise_4/.gitignore @@ -0,0 +1,3 @@ +/.idea +/out +/exercise_4.iml \ No newline at end of file diff --git a/part_4/exercise_4/Main.java b/part_4/exercise_4/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..5ffb3e1bd7060cefc1b6814b3197fc8f5d4d82e2 --- /dev/null +++ b/part_4/exercise_4/Main.java @@ -0,0 +1,11 @@ +package fi.utu.tech.ooj.exercise4; + +import fi.utu.tech.ooj.exercise4.exercise4.Exercise4; + +public class Main { + public static void main(String[] args) throws Exception { + System.out.println("Advanced Course in Object-Oriented Programming, Part 4 Exercises"); + + new Exercise4(); + } +} diff --git a/part_4/exercise_4/books.zip b/part_4/exercise_4/books.zip new file mode 100644 index 0000000000000000000000000000000000000000..7c87031bc1d5b835078c9379313ecb4486c09852 Binary files /dev/null and b/part_4/exercise_4/books.zip differ diff --git a/part_4/exercise_4/exercise4/Exercise4.java b/part_4/exercise_4/exercise4/Exercise4.java new file mode 100644 index 0000000000000000000000000000000000000000..d297a12a20c36b28993eb279ededc8cd8e56f18b --- /dev/null +++ b/part_4/exercise_4/exercise4/Exercise4.java @@ -0,0 +1,27 @@ +package fi.utu.tech.ooj.exercise4.exercise4; + +import java.io.IOException; + +public class Exercise4 { + public Exercise4() { + System.out.println("Exercise 4"); + + System.out.println("Exercise 1"); + try (var zipper = new TestZipper("books.zip")) { + zipper.run(); + } catch (IOException e) { + System.err.println("Execution failed!"); + e.printStackTrace(); + } + + System.out.println(); + System.out.println("-----------------------------------------"); + System.out.println("Exercise 2"); + try (var zipper = new TestZipper2("books.zip")) { + zipper.run(); + } catch (IOException e) { + System.err.println("Execution failed!"); + e.printStackTrace(); + } + } +} diff --git a/part_4/exercise_4/exercise4/TestZipper.java b/part_4/exercise_4/exercise4/TestZipper.java new file mode 100644 index 0000000000000000000000000000000000000000..037c6c12ccb2513fb447e2f2e135187bf4ba0982 --- /dev/null +++ b/part_4/exercise_4/exercise4/TestZipper.java @@ -0,0 +1,54 @@ +package fi.utu.tech.ooj.exercise4.exercise4; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Pattern; + +/** + * Test zipper. + * <p> + * Extracts zip, iterates through the files and prints information from each file. + * <p> + * From each file there will be printed: + * - name + * - amount of lines + * - amount of words + */ +class TestZipper extends Zipper<Void> { + TestZipper(String zipFile) throws IOException { + super(zipFile); + } + + @Override + protected Handler<Void> createHandler(Path file) { + return new Handler<>(file) { + @Override + public Void handle() throws IOException { + var regex = Pattern.compile("\\W"); + var contents = Files.readString(file); + var lines = Files.readAllLines(file); + var firstLine = lines.isEmpty() ? "unknown" : lines.getFirst(); + var words = regex.splitAsStream(contents).filter(s -> !s.isBlank()).map(String::toLowerCase).toList(); + + System.out.printf(""" + + Originally was fetched from %s. + The founded file is %s. + The file contains %d lines. + The file contains %d words. + Possible title of the work: %s + + """, + tempDirectory, + file.getFileName(), + lines.size(), + words.size(), + firstLine + ); + + return null; + } + }; + } +} diff --git a/part_4/exercise_4/exercise4/TestZipper2.java b/part_4/exercise_4/exercise4/TestZipper2.java new file mode 100644 index 0000000000000000000000000000000000000000..d1115ecb89f5dd9b9a555d09c2773dda1c7b773d --- /dev/null +++ b/part_4/exercise_4/exercise4/TestZipper2.java @@ -0,0 +1,239 @@ +package fi.utu.tech.ooj.exercise4.exercise4; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toCollection; + +class Book implements Comparable<Book> { + final Path file; + final String title; + final int numberOfLines; + List<String> words; + List<String> uniqueWords; + + public Book(Path file, String title, int numberOfLines) { + Objects.requireNonNull(file); + Objects.requireNonNull(title); + this.file = file; + this.title = title; + this.numberOfLines = numberOfLines; + } + + @Override + public int compareTo(Book book) { + return title.compareTo(book.getTitle()); + } + + @Override + public String toString() { + return "File: " + file + + ". Title:" + title + + ". Number of lines: " + numberOfLines; + } + + public List<String> words() throws IOException { + if (words == null) { + var regex = Pattern.compile("\\W"); + var contents = Files.readString(file); + + words = regex.splitAsStream(contents).filter(s -> !s.isBlank()).map(String::toLowerCase).toList(); + } + + return words; + } + + public List<String> uniqueWords() throws IOException { + if (uniqueWords == null) { + List<String> words = words(); + + uniqueWords = words.stream() + .distinct() + .toList(); + } + + return uniqueWords; + } + + public String getTitle() { + return title; + } + + public int getNumberOfLines() { + return numberOfLines; + } +} + +interface BookSorter<B extends Book> { + <T extends B> List<T> sortByTitleAsc(List<T> books); + + <T extends B> List<T> sortByNumberOfLineAsc(List<T> books); + + <T extends B> List<T> sortByNumberOfUniqueWordsDesc(List<T> books); + + <T extends B> List<T> sortByTitleAscThenNumberOfUniqueWordsDesc(List<T> books); +} + +class MutableBookSorter<B extends Book> implements BookSorter<B> { + private final Comparator<B> naturalComparator; + private final Comparator<B> lineCountComparator; + private final Comparator<B> uniqueWordsComparator; + + public MutableBookSorter() { + naturalComparator = Comparator.naturalOrder(); + lineCountComparator = Comparator.comparing(B::getNumberOfLines); + uniqueWordsComparator = (book1, book2) -> { + int uniqueWordsCount1, uniqueWordsCount2; + + try { + uniqueWordsCount1 = book1.uniqueWords().size(); + uniqueWordsCount2 = book2.uniqueWords().size(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return uniqueWordsCount2 - uniqueWordsCount1; + }; + } + + @Override + public <T extends B> List<T> sortByTitleAsc(List<T> books) { + books.sort(naturalComparator); + + return books; + } + + @Override + public <T extends B> List<T> sortByNumberOfLineAsc(List<T> books) { + books.sort(lineCountComparator); + + return books; + } + + @Override + public <T extends B> List<T> sortByNumberOfUniqueWordsDesc(List<T> books) { + books.sort(uniqueWordsComparator); + + return books; + } + + @Override + public <T extends B> List<T> sortByTitleAscThenNumberOfUniqueWordsDesc(List<T> books) { + books.sort(naturalComparator.thenComparing(uniqueWordsComparator)); + + return books; + } +} + +class ImmutableBookSorter<B extends Book> extends MutableBookSorter<B> { + @Override + public <T extends B> List<T> sortByTitleAsc(List<T> books) { + var copiedBooks = copy(books); + + return super.sortByTitleAsc(copiedBooks); + } + + @Override + public <T extends B> List<T> sortByNumberOfLineAsc(List<T> books) { + var copiedBooks = copy(books); + + return super.sortByNumberOfLineAsc(copiedBooks); + } + + @Override + public <T extends B> List<T> sortByNumberOfUniqueWordsDesc(List<T> books) { + var copiedBooks = copy(books); + + return super.sortByNumberOfUniqueWordsDesc(copiedBooks); + } + + @Override + public <T extends B> List<T> sortByTitleAscThenNumberOfUniqueWordsDesc(List<T> books) { + var copiedBooks = copy(books); + + return super.sortByTitleAscThenNumberOfUniqueWordsDesc(copiedBooks); + } + + private <T extends B> List<T> copy(List<T> books) { + return new ArrayList<>(books); + } +} + +class TestZipper2 extends Zipper<Book> { + TestZipper2(String zipFile) throws IOException { + super(zipFile); + } + + @Override + protected Handler<Book> createHandler(Path file) { + return new Handler<>(file) { + @Override + public Book handle() throws IOException { + var lines = Files.readAllLines(file); + var firstLine = lines.isEmpty() ? "unknown" : lines.getFirst(); + + return new Book(file, firstLine, lines.size()); + } + }; + } + + @Override + protected FileObjectsHandler<Book> createFileObjectsHandler() { + return books -> { + List<Book> sortedBooks; + BookSorter<Book> sorter = new ImmutableBookSorter<>(); + Consumer<Book> printBookInformation = (Book book) -> { + System.out.println("Book: " + book); + + try { + List<String> uniqueWords = book.uniqueWords(); + + System.out.println("Unique words: " + uniqueWords); + System.out.println("Unique words count: " + uniqueWords.size()); + System.out.println(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + + System.out.println(); + System.out.println("Original books:"); + books.forEach(System.out::println); + + System.out.printf(""" + + Handled %d Books. + Now we could sort it out a bit. + + """, books.size()); + + System.out.println("1. Sort by title (asc):"); + sortedBooks = sorter.sortByTitleAsc(books); + sortedBooks.forEach(System.out::println); + + System.out.println(); + System.out.println("2. Sort by number of lines (asc):"); + sortedBooks = sorter.sortByNumberOfLineAsc(books); + sortedBooks.forEach(System.out::println); + + System.out.println(); + System.out.println("3. Sort by number of unique words (desc):"); + sortedBooks = sorter.sortByNumberOfUniqueWordsDesc(books); + sortedBooks.forEach(printBookInformation); + + System.out.println(); + System.out.println("4. Sort by title (asc) then number of unique words (desc):"); + sortedBooks = sorter.sortByTitleAscThenNumberOfUniqueWordsDesc(books); + sortedBooks.forEach(printBookInformation); + + System.out.println(); + System.out.println("Original books:"); + books.forEach(System.out::println); + }; + } +} diff --git a/part_4/exercise_4/exercise4/Zipper.java b/part_4/exercise_4/exercise4/Zipper.java new file mode 100644 index 0000000000000000000000000000000000000000..050aa2851b9c86da5ddb24c4bb90254d8836ca10 --- /dev/null +++ b/part_4/exercise_4/exercise4/Zipper.java @@ -0,0 +1,192 @@ +package fi.utu.tech.ooj.exercise4.exercise4; + +import fi.utu.tech.ooj.exercise4.Main; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.zip.ZipInputStream; + +// WORKAROUND: if the zip file is not found, copy books.zip from the resources directory +// to the project's root and follow the two instructions below, +// also marked with WORKAROUND comments. + +/** + +A class that models unzipping (extracting a compressed zip package). +<p> +The idea is that while an object of the class exists, there is also a temporary directory +created by the object on the disk. When the object is closed, the directory is also deleted. +<p> +How to use it? Create an object. Creation assumes that the zip file must exist. +The class's 'run' method activates the unzipping. Finally, close the object ('close'). +<p> +Hint: closing is easy with Java's try-with-resources feature. +*/ +abstract public class Zipper<E> implements AutoCloseable { + // zip-file for unzipping + private final String zipFile; + + // java class, from which package the zip file is looked for + private final Class<?> resolver = Main.class; + + // path of temp directory + protected final Path tempDirectory; + + private final List<E> objects; + + /** + * Records the given zip file and + * creates a temporary directory 'tempDirectory'. + * + * @param zipFile Zip file path (precondition: must exist and be non-null). + * @throws IOException If the zip file is not found or the temporary directory cannot be created. + */ + public Zipper(String zipFile) throws IOException { + // WORKAROUND: if the zip file is not found, comment out the next two lines. + if (resolver.getResource(zipFile) == null) + throw new FileNotFoundException(zipFile); + + this.zipFile = zipFile; + this.objects = new ArrayList<>(); + + tempDirectory = Files.createTempDirectory("dtek0066"); + System.out.println("Created a temp directory " + tempDirectory); + } + + /** + * Deletes the temporary directory 'tempDirectory' when the object is closed. + * + * @throws IOException In case of any I/O errors. + */ + @Override + public void close() throws IOException { + try (final var stream = Files.walk(tempDirectory)) { + stream + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + + System.out.println("The removed temp directory was " + tempDirectory); + } + + /** + * Unzip the file 'zipFile' to the temporary directory 'tempDirectory'. + * + * @throws IOException In case of any I/O errors. + */ + private void unzip() throws IOException { + final var destinationDir = tempDirectory.toFile(); + + // WORKAROUND: If the zip file is not found, change to the following + // try (final var inputStream = new FileInputStream(zipFile); + try (final var inputStream = resolver.getResourceAsStream(zipFile); + final var stream = new ZipInputStream(inputStream)) { + var zipEntry = stream.getNextEntry(); + while (zipEntry != null) { + final var newFile = new File(destinationDir, zipEntry.getName()); + final var destDirPath = destinationDir.getCanonicalPath(); + final var destFilePath = newFile.getCanonicalPath(); + if (!destFilePath.startsWith(destDirPath + File.separator)) { + throw new IOException("Entry is outside of the target dir: " + zipEntry.getName()); + } + System.out.println("Puretaan " + newFile); + if (zipEntry.isDirectory()) { + if (!newFile.isDirectory() && !newFile.mkdirs()) { + throw new IOException("Failed to create directory: " + newFile); + } + } else { + // fix for Windows-created archives + final var parent = newFile.getParentFile(); + if (!parent.isDirectory() && !parent.mkdirs()) { + throw new IOException("Failed to create directory: " + parent); + } + + // write file content + try (final var fos = new FileOutputStream(newFile)) { + stream.transferTo(fos); + } + } + zipEntry = stream.getNextEntry(); + } + stream.closeEntry(); + } + } + + /** + * Executes unzipping and creates a handler for every created file. + * + * @throws IOException In case of any I/O errors. + */ + public void run() throws IOException { + unzip(); + + for (final var handler : createHandlers()) { + E object = handler.handle(); + + if (object != null) { + objects.add(object); + } + } + + var fileObjectsHandler = createFileObjectsHandler(); + fileObjectsHandler.handle(objects); + } + + protected List<Handler<E>> createHandlers() throws IOException { + try (final var stream = Files.list(tempDirectory)) { + return stream.map(this::createHandler).toList(); + } + } + + /** + * Creation of the Handler. + * + * @param file The file to be handerl (precondition: must exist and be non-null) + * @return Handler + */ + protected abstract Handler<E> createHandler(Path file); + + protected FileObjectsHandler<E> createFileObjectsHandler() { + return fileObjects -> { + }; + } + + /** + * A handler for a single file, responsible for processing + * an individual file. + */ + protected abstract static class Handler<E> { + public final Path file; + + /** + * Initializes the handler. + * + * @param file The file to be handled (precondition: must exist and be non-null) + */ + public Handler(Path file) { + this.file = file; + } + + /** + * Processes the file. + * + * @throws IOException In case of any I/O errors. + */ + abstract public E handle() throws IOException; + } + + protected interface FileObjectsHandler<E> { + void handle(List<E> fileObjects); + } +} + + +