Learn Java in 10 DaysDay 10: Final Project

Day 10: Final Project

Today's Goal

Put everything you've learned over the past 9 days into practice by building a console-based task management application.


Project Overview

Item Details
App name TaskManager
Features CRUD operations, file persistence, search and filter
Technologies Classes, inheritance, collections, file I/O, Stream API
flowchart TB
    subgraph App["TaskManager"]
        UI["Main Menu<br>User Input"]
        Service["TaskService<br>Business Logic"]
        Storage["FileStorage<br>File I/O"]
        Model["Task / Priority<br>Data Model"]
    end
    UI --> Service --> Storage
    Service --> Model
    style UI fill:#3b82f6,color:#fff
    style Service fill:#22c55e,color:#fff
    style Storage fill:#f59e0b,color:#fff
    style Model fill:#8b5cf6,color:#fff

Step 1: Data Model

Priority (Enum)

enum Priority {
    HIGH("High"), MEDIUM("Medium"), LOW("Low");

    private final String label;

    Priority(String label) {
        this.label = label;
    }

    String getLabel() { return label; }
}

Task (Class)

import java.time.LocalDate;

class Task {
    private static int nextId = 1;

    private final int id;
    private String title;
    private String description;
    private Priority priority;
    private boolean completed;
    private final LocalDate createdDate;

    Task(String title, String description, Priority priority) {
        this.id = nextId++;
        this.title = title;
        this.description = description;
        this.priority = priority;
        this.completed = false;
        this.createdDate = LocalDate.now();
    }

    // Constructor for restoring from file
    Task(int id, String title, String description,
         Priority priority, boolean completed, LocalDate createdDate) {
        this.id = id;
        this.title = title;
        this.description = description;
        this.priority = priority;
        this.completed = completed;
        this.createdDate = createdDate;
        if (id >= nextId) {
            nextId = id + 1;
        }
    }

    // Getters
    int getId() { return id; }
    String getTitle() { return title; }
    String getDescription() { return description; }
    Priority getPriority() { return priority; }
    boolean isCompleted() { return completed; }
    LocalDate getCreatedDate() { return createdDate; }

    // Setters
    void setTitle(String title) { this.title = title; }
    void setDescription(String description) { this.description = description; }
    void setPriority(Priority priority) { this.priority = priority; }
    void setCompleted(boolean completed) { this.completed = completed; }

    // CSV conversion
    String toCsv() {
        return String.join(",",
            String.valueOf(id),
            title,
            description,
            priority.name(),
            String.valueOf(completed),
            createdDate.toString()
        );
    }

    static Task fromCsv(String csv) {
        String[] parts = csv.split(",", 6);
        return new Task(
            Integer.parseInt(parts[0]),
            parts[1],
            parts[2],
            Priority.valueOf(parts[3]),
            Boolean.parseBoolean(parts[4]),
            LocalDate.parse(parts[5])
        );
    }

    @Override
    public String toString() {
        String status = completed ? "Done" : "Todo";
        return String.format("[%s] #%d %s [%s] - %s (%s)",
            status, id, title, priority.getLabel(), description, createdDate);
    }
}

Step 2: File Storage

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

class FileStorage {
    private final Path filePath;

    FileStorage(String filename) {
        this.filePath = Path.of(filename);
    }

    List<Task> load() {
        List<Task> tasks = new ArrayList<>();
        try {
            if (Files.exists(filePath)) {
                List<String> lines = Files.readAllLines(filePath);
                for (String line : lines) {
                    if (!line.isBlank()) {
                        tasks.add(Task.fromCsv(line));
                    }
                }
            }
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
        return tasks;
    }

    void save(List<Task> tasks) {
        try {
            List<String> lines = tasks.stream()
                .map(Task::toCsv)
                .toList();
            Files.write(filePath, lines);
        } catch (IOException e) {
            System.err.println("Error writing file: " + e.getMessage());
        }
    }
}

Step 3: Business Logic

import java.util.*;
import java.util.stream.Collectors;

class TaskService {
    private final List<Task> tasks;
    private final FileStorage storage;

    TaskService(FileStorage storage) {
        this.storage = storage;
        this.tasks = new ArrayList<>(storage.load());
    }

    // Add task
    Task addTask(String title, String description, Priority priority) {
        Task task = new Task(title, description, priority);
        tasks.add(task);
        storage.save(tasks);
        return task;
    }

    // Complete task
    boolean completeTask(int id) {
        return findById(id).map(task -> {
            task.setCompleted(true);
            storage.save(tasks);
            return true;
        }).orElse(false);
    }

    // Delete task
    boolean deleteTask(int id) {
        boolean removed = tasks.removeIf(t -> t.getId() == id);
        if (removed) {
            storage.save(tasks);
        }
        return removed;
    }

    // Get all tasks
    List<Task> getAllTasks() {
        return Collections.unmodifiableList(tasks);
    }

    // Get pending tasks
    List<Task> getPendingTasks() {
        return tasks.stream()
            .filter(t -> !t.isCompleted())
            .toList();
    }

    // Filter by priority
    List<Task> getTasksByPriority(Priority priority) {
        return tasks.stream()
            .filter(t -> t.getPriority() == priority)
            .toList();
    }

    // Search by keyword
    List<Task> search(String keyword) {
        String lower = keyword.toLowerCase();
        return tasks.stream()
            .filter(t -> t.getTitle().toLowerCase().contains(lower)
                      || t.getDescription().toLowerCase().contains(lower))
            .toList();
    }

    // Statistics
    Map<String, Object> getStatistics() {
        Map<String, Object> stats = new LinkedHashMap<>();
        stats.put("Total tasks", tasks.size());
        stats.put("Completed", tasks.stream().filter(Task::isCompleted).count());
        stats.put("Pending", tasks.stream().filter(t -> !t.isCompleted()).count());

        Map<Priority, Long> byPriority = tasks.stream()
            .collect(Collectors.groupingBy(Task::getPriority, Collectors.counting()));
        stats.put("By priority", byPriority);

        return stats;
    }

    // Find by ID
    private Optional<Task> findById(int id) {
        return tasks.stream()
            .filter(t -> t.getId() == id)
            .findFirst();
    }
}

Step 4: Main Menu

import java.util.List;
import java.util.Scanner;

public class TaskManager {
    private final TaskService service;
    private final Scanner scanner;

    TaskManager() {
        FileStorage storage = new FileStorage("tasks.csv");
        this.service = new TaskService(storage);
        this.scanner = new Scanner(System.in);
    }

    void run() {
        System.out.println("=== Task Manager ===");

        while (true) {
            showMenu();
            String choice = scanner.nextLine().trim();

            switch (choice) {
                case "1" -> addTask();
                case "2" -> listTasks(service.getAllTasks());
                case "3" -> listTasks(service.getPendingTasks());
                case "4" -> completeTask();
                case "5" -> deleteTask();
                case "6" -> searchTasks();
                case "7" -> filterByPriority();
                case "8" -> showStatistics();
                case "0" -> {
                    System.out.println("Goodbye!");
                    return;
                }
                default -> System.out.println("Invalid selection.");
            }
            System.out.println();
        }
    }

    private void showMenu() {
        System.out.println("""
            --- Menu ---
            1. Add task
            2. Show all tasks
            3. Show pending tasks
            4. Complete task
            5. Delete task
            6. Search by keyword
            7. Filter by priority
            8. Statistics
            0. Exit
            """);
        System.out.print("Choice: ");
    }

    private void addTask() {
        System.out.print("Title: ");
        String title = scanner.nextLine();

        System.out.print("Description: ");
        String description = scanner.nextLine();

        System.out.print("Priority (1: High, 2: Medium, 3: Low): ");
        Priority priority = switch (scanner.nextLine().trim()) {
            case "1" -> Priority.HIGH;
            case "3" -> Priority.LOW;
            default -> Priority.MEDIUM;
        };

        Task task = service.addTask(title, description, priority);
        System.out.println("Added: " + task);
    }

    private void listTasks(List<Task> tasks) {
        if (tasks.isEmpty()) {
            System.out.println("No tasks found.");
            return;
        }
        tasks.forEach(System.out::println);
        System.out.printf("(%d tasks)%n", tasks.size());
    }

    private void completeTask() {
        System.out.print("Task ID to complete: ");
        try {
            int id = Integer.parseInt(scanner.nextLine().trim());
            if (service.completeTask(id)) {
                System.out.println("Task #" + id + " completed.");
            } else {
                System.out.println("Task not found.");
            }
        } catch (NumberFormatException e) {
            System.out.println("Invalid ID.");
        }
    }

    private void deleteTask() {
        System.out.print("Task ID to delete: ");
        try {
            int id = Integer.parseInt(scanner.nextLine().trim());
            if (service.deleteTask(id)) {
                System.out.println("Task #" + id + " deleted.");
            } else {
                System.out.println("Task not found.");
            }
        } catch (NumberFormatException e) {
            System.out.println("Invalid ID.");
        }
    }

    private void searchTasks() {
        System.out.print("Keyword: ");
        String keyword = scanner.nextLine();
        listTasks(service.search(keyword));
    }

    private void filterByPriority() {
        System.out.print("Priority (1: High, 2: Medium, 3: Low): ");
        Priority priority = switch (scanner.nextLine().trim()) {
            case "1" -> Priority.HIGH;
            case "3" -> Priority.LOW;
            default -> Priority.MEDIUM;
        };
        listTasks(service.getTasksByPriority(priority));
    }

    private void showStatistics() {
        var stats = service.getStatistics();
        System.out.println("=== Statistics ===");
        stats.forEach((key, value) ->
            System.out.printf("%s: %s%n", key, value));
    }

    public static void main(String[] args) {
        new TaskManager().run();
    }
}

10-Day Recap

flowchart TB
    D1["Day 1<br>Java Basics<br>Hello World"]
    D2["Day 2<br>Variables &<br>Data Types"]
    D3["Day 3<br>Control Flow"]
    D4["Day 4<br>Arrays &<br>Strings"]
    D5["Day 5<br>Methods"]
    D6["Day 6<br>Classes &<br>Objects"]
    D7["Day 7<br>Inheritance &<br>Polymorphism"]
    D8["Day 8<br>Exceptions &<br>File I/O"]
    D9["Day 9<br>Collections &<br>Generics"]
    D10["Day 10<br>Final Project"]
    D1 --> D2 --> D3 --> D4 --> D5
    D5 --> D6 --> D7 --> D8 --> D9 --> D10
    style D1 fill:#3b82f6,color:#fff
    style D2 fill:#3b82f6,color:#fff
    style D3 fill:#3b82f6,color:#fff
    style D4 fill:#3b82f6,color:#fff
    style D5 fill:#3b82f6,color:#fff
    style D6 fill:#22c55e,color:#fff
    style D7 fill:#22c55e,color:#fff
    style D8 fill:#f59e0b,color:#fff
    style D9 fill:#f59e0b,color:#fff
    style D10 fill:#8b5cf6,color:#fff
Day Topic Key Concepts
1 Welcome to Java JDK, JVM, main, println
2 Variables and Data Types Primitives, String, operators
3 Control Flow if, switch, for, while
4 Arrays and Strings Arrays, String, StringBuilder
5 Methods Parameters, return values, overloading, recursion
6 Classes and Objects Constructors, encapsulation, static, records
7 Inheritance and Polymorphism extends, interface, abstract, sealed
8 Exceptions and File I/O try-catch, Files, Path
9 Collections and Generics List, Map, Set, Stream API
10 Final Project Integrating all concepts

What's Next

You've mastered the fundamentals of Java. Here are some topics to explore next.

Topic Description
Lambda expressions and functional interfaces Deeper understanding of the Stream API
Multithreading Concurrent programming, ExecutorService
Databases JDBC, JPA
Web frameworks Spring Boot
Build tools Maven, Gradle
Testing JUnit 5, Mockito
Design patterns GoF patterns

Exercises

Exercise 1: Feature Addition

Add an edit feature to the TaskManager that allows updating a task's title, description, and priority.

Exercise 2: Applied

Add a due date (dueDate) field to tasks and implement a feature to display overdue tasks.

Challenge

Change the storage format from CSV to JSON. Without using any external libraries, implement your own JSON string generation and parsing.


References


Congratulations! You've completed the 10-day Java fundamentals course. The knowledge you've gained here forms a solid foundation for web application development, Android development, enterprise systems, and any other area where Java is used.