File Handling and I/O

Practice and Assessment

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

Project Overview

এই module-এর final project-এ আমরা একটি small file-based course storage system তৈরি করব।

Systemটি support করবে:

  • Course save
  • Course load
  • Course list
  • Course delete
  • UTF-8 text storage
  • Safe path handling
  • Atomic replacement
  • Repository abstraction
  • Missing course handling
  • Storage exception translation
  • Backup creation

এই project-এর goal production-ready database বানানো নয়।

Goal হলো Module 6-এর core I/O concepts একসঙ্গে apply করা।


Project Requirements

আমাদের repository store করবে:

Course code
Title
Price
Status

File layout:

data/
└── courses/
    ├── java-oop.properties
    ├── backend.properties
    └── system-design.properties

Repository contract:

save(...)
findByCode(...)
findAll()
deleteByCode(...)

Additional operation:

backupTo(...)

Project Structure

src/main/java/io/liveklass/
├── Main.java
├── course/
│   ├── Course.java
│   ├── CourseCode.java
│   └── CourseStatus.java
└── repository/
    ├── CourseRepository.java
    ├── CourseRepositoryException.java
    └── FileCourseRepository.java

Part 1: Course Status

CourseStatus.java

package io.liveklass.course;

public enum CourseStatus {

    DRAFT,
    REVIEW,
    PUBLISHED,
    ARCHIVED
}

Part 2: Course Code

CourseCode.java

package io.liveklass.course;

import java.util.Objects;

public final class CourseCode {

    private final String value;

    public CourseCode(
            String value
    ) {
        if (
                value == null
                || value.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Course code is required."
            );
        }

        String normalized =
                value.strip()
                        .toUpperCase();

        if (
                !normalized.matches(
                        "[A-Z0-9-]+"
                )
        ) {
            throw new IllegalArgumentException(
                    "Course code contains unsupported characters."
            );
        }

        this.value =
                normalized;
    }

    public String getValue() {
        return value;
    }

    @Override
    public boolean equals(
            Object other
    ) {
        if (this == other) {
            return true;
        }

        if (
                !(other
                instanceof CourseCode courseCode)
        ) {
            return false;
        }

        return value.equals(
                courseCode.value
        );
    }

    @Override
    public int hashCode() {
        return Objects.hash(
                value
        );
    }

    @Override
    public String toString() {
        return value;
    }
}

Part 3: Course Domain Object

Course.java

package io.liveklass.course;

public final class Course {

    private final CourseCode code;
    private final String title;
    private final long priceInPaisa;
    private final CourseStatus status;

    public Course(
            CourseCode code,
            String title,
            long priceInPaisa,
            CourseStatus status
    ) {
        if (code == null) {
            throw new IllegalArgumentException(
                    "Course code is required."
            );
        }

        if (
                title == null
                || title.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Course title is required."
            );
        }

        if (priceInPaisa < 0) {
            throw new IllegalArgumentException(
                    "Course price cannot be negative."
            );
        }

        if (status == null) {
            throw new IllegalArgumentException(
                    "Course status is required."
            );
        }

        this.code = code;
        this.title = title.strip();
        this.priceInPaisa = priceInPaisa;
        this.status = status;
    }

    public CourseCode getCode() {
        return code;
    }

    public String getTitle() {
        return title;
    }

    public long getPriceInPaisa() {
        return priceInPaisa;
    }

    public CourseStatus getStatus() {
        return status;
    }

    @Override
    public String toString() {
        return code
                + " — "
                + title
                + " — "
                + status;
    }
}

Part 4: Repository Contract

CourseRepository.java

package io.liveklass.repository;

import io.liveklass.course.Course;
import io.liveklass.course.CourseCode;

import java.nio.file.Path;
import java.util.List;

public interface CourseRepository {

    void save(
            Course course
    );

    Course findByCode(
            CourseCode courseCode
    );

    List<Course> findAll();

    boolean deleteByCode(
            CourseCode courseCode
    );

    void backupTo(
            Path backupDirectory
    );
}

Part 5: Repository Exception

CourseRepositoryException.java

package io.liveklass.repository;

public final class CourseRepositoryException
        extends RuntimeException {

    public CourseRepositoryException(
            String message
    ) {
        super(
                message
        );
    }

    public CourseRepositoryException(
            String message,
            Throwable cause
    ) {
        super(
                message,
                cause
        );
    }
}

Part 6: File Repository

FileCourseRepository.java

package io.liveklass.repository;

import io.liveklass.course.Course;
import io.liveklass.course.CourseCode;
import io.liveklass.course.CourseStatus;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;

public final class FileCourseRepository
        implements CourseRepository {

    private final Path baseDirectory;

    public FileCourseRepository(
            Path baseDirectory
    ) {
        if (baseDirectory == null) {
            throw new IllegalArgumentException(
                    "Base directory is required."
            );
        }

        this.baseDirectory =
                baseDirectory
                        .toAbsolutePath()
                        .normalize();

        initializeStorage();
    }

    @Override
    public void save(
            Course course
    ) {
        if (course == null) {
            throw new IllegalArgumentException(
                    "Course is required."
            );
        }

        Path target =
                resolveCourseFile(
                        course.getCode()
                );

        Properties properties =
                serialize(
                        course
                );

        try {
            replaceAtomically(
                    target,
                    properties
            );
        } catch (
            IOException exception
        ) {
            throw new CourseRepositoryException(
                    "Could not save course "
                    + course.getCode()
                    + ".",
                    exception
            );
        }
    }

    @Override
    public Course findByCode(
            CourseCode courseCode
    ) {
        Path path =
                resolveCourseFile(
                        courseCode
                );

        try {
            return deserialize(
                    loadProperties(
                            path
                    ),
                    path
            );
        } catch (
            NoSuchFileException exception
        ) {
            return null;
        } catch (
            IOException exception
        ) {
            throw new CourseRepositoryException(
                    "Could not load course "
                    + courseCode
                    + ".",
                    exception
            );
        }
    }

    @Override
    public List<Course> findAll() {
        try (
                Stream<Path> files =
                        Files.list(
                                baseDirectory
                        )
        ) {
            return files.filter(
                    Files::isRegularFile
            )
            .filter(
                    this::isCourseFile
            )
            .sorted()
            .map(
                    this::loadCourse
            )
            .toList();
        } catch (
            IOException exception
        ) {
            throw new CourseRepositoryException(
                    "Could not list courses.",
                    exception
            );
        }
    }

    @Override
    public boolean deleteByCode(
            CourseCode courseCode
    ) {
        Path path =
                resolveCourseFile(
                        courseCode
                );

        try {
            return Files.deleteIfExists(
                    path
            );
        } catch (
            IOException exception
        ) {
            throw new CourseRepositoryException(
                    "Could not delete course "
                    + courseCode
                    + ".",
                    exception
            );
        }
    }

    @Override
    public void backupTo(
            Path backupDirectory
    ) {
        if (backupDirectory == null) {
            throw new IllegalArgumentException(
                    "Backup directory is required."
            );
        }

        Path targetRoot =
                backupDirectory
                        .toAbsolutePath()
                        .normalize();

        try {
            Files.createDirectories(
                    targetRoot
            );

            try (
                    Stream<Path> paths =
                            Files.walk(
                                    baseDirectory
                            )
            ) {
                paths.forEach(
                        source -> {
                            Path relative =
                                    baseDirectory.relativize(
                                            source
                                    );

                            Path target =
                                    targetRoot.resolve(
                                            relative
                                    );

                            try {
                                if (
                                        Files.isDirectory(
                                                source
                                        )
                                ) {
                                    Files.createDirectories(
                                            target
                                    );
                                } else {
                                    Files.copy(
                                            source,
                                            target,
                                            StandardCopyOption.REPLACE_EXISTING
                                    );
                                }
                            } catch (
                                IOException exception
                            ) {
                                throw new UncheckedIOException(
                                        exception
                                );
                            }
                        }
                );
            }
        } catch (
            UncheckedIOException exception
        ) {
            throw new CourseRepositoryException(
                    "Could not create course backup.",
                    exception.getCause()
            );
        } catch (
            IOException exception
        ) {
            throw new CourseRepositoryException(
                    "Could not create course backup.",
                    exception
            );
        }
    }

    private void initializeStorage() {
        try {
            Files.createDirectories(
                    baseDirectory
            );
        } catch (
            IOException exception
        ) {
            throw new CourseRepositoryException(
                    "Could not initialize course storage.",
                    exception
            );
        }
    }

    private Path resolveCourseFile(
            CourseCode courseCode
    ) {
        if (courseCode == null) {
            throw new IllegalArgumentException(
                    "Course code is required."
            );
        }

        String fileName =
                courseCode.getValue()
                        .toLowerCase()
                        + ".properties";

        Path target =
                baseDirectory.resolve(
                        fileName
                )
                .normalize();

        if (
                !target.startsWith(
                        baseDirectory
                )
        ) {
            throw new IllegalArgumentException(
                    "Course path escapes storage directory."
            );
        }

        return target;
    }

    private Properties serialize(
            Course course
    ) {
        Properties properties =
                new Properties();

        properties.setProperty(
                "code",
                course.getCode()
                        .getValue()
        );

        properties.setProperty(
                "title",
                course.getTitle()
        );

        properties.setProperty(
                "priceInPaisa",
                Long.toString(
                        course.getPriceInPaisa()
                )
        );

        properties.setProperty(
                "status",
                course.getStatus()
                        .name()
        );

        return properties;
    }

    private Course deserialize(
            Properties properties,
            Path source
    ) {
        try {
            CourseCode code =
                    new CourseCode(
                            requireProperty(
                                    properties,
                                    "code"
                            )
                    );

            String title =
                    requireProperty(
                            properties,
                            "title"
                    );

            long priceInPaisa =
                    Long.parseLong(
                            requireProperty(
                                    properties,
                                    "priceInPaisa"
                            )
                    );

            CourseStatus status =
                    CourseStatus.valueOf(
                            requireProperty(
                                    properties,
                                    "status"
                            )
                    );

            return new Course(
                    code,
                    title,
                    priceInPaisa,
                    status
            );
        } catch (
            CourseRepositoryException exception
        ) {
            throw exception;
        } catch (
            RuntimeException exception
        ) {
            throw new CourseRepositoryException(
                    "Stored course data is invalid: "
                    + source.getFileName()
                    + ".",
                    exception
            );
        }
    }

    private String requireProperty(
            Properties properties,
            String key
    ) {
        String value =
                properties.getProperty(
                        key
                );

        if (
                value == null
                || value.isBlank()
        ) {
            throw new CourseRepositoryException(
                    "Stored course is missing property: "
                    + key
                    + "."
            );
        }

        return value;
    }

    private Properties loadProperties(
            Path path
    ) throws IOException {
        Properties properties =
                new Properties();

        try (
                BufferedReader reader =
                        Files.newBufferedReader(
                                path,
                                StandardCharsets.UTF_8
                        )
        ) {
            properties.load(
                    reader
            );
        }

        return properties;
    }

    private void storeProperties(
            Path path,
            Properties properties
    ) throws IOException {
        try (
                BufferedWriter writer =
                        Files.newBufferedWriter(
                                path,
                                StandardCharsets.UTF_8
                        )
        ) {
            properties.store(
                    writer,
                    null
            );
        }
    }

    private void replaceAtomically(
            Path target,
            Properties properties
    ) throws IOException {
        Path temp =
                Files.createTempFile(
                        baseDirectory,
                        "course-",
                        ".tmp"
                );

        try {
            storeProperties(
                    temp,
                    properties
            );

            try {
                Files.move(
                        temp,
                        target,
                        StandardCopyOption.REPLACE_EXISTING,
                        StandardCopyOption.ATOMIC_MOVE
                );
            } catch (
                AtomicMoveNotSupportedException exception
            ) {
                Files.move(
                        temp,
                        target,
                        StandardCopyOption.REPLACE_EXISTING
                );
            }
        } finally {
            Files.deleteIfExists(
                    temp
            );
        }
    }

    private boolean isCourseFile(
            Path path
    ) {
        return path.getFileName()
                .toString()
                .endsWith(
                        ".properties"
                );
    }

    private Course loadCourse(
            Path path
    ) {
        try {
            return deserialize(
                    loadProperties(
                            path
                    ),
                    path
            );
        } catch (
            IOException exception
        ) {
            throw new CourseRepositoryException(
                    "Could not load stored course "
                    + path.getFileName()
                    + ".",
                    exception
            );
        }
    }
}

Design Review

এই repository several important design decisions নেয়।

Safe Paths

Course code directly arbitrary path নয়।

CourseCode

already restricts values।

Repository still:

normalize()
startsWith(baseDirectory)

check করে।


UTF-8

Reading:

Files.newBufferedReader(
        path,
        StandardCharsets.UTF_8
)

Writing:

Files.newBufferedWriter(
        path,
        StandardCharsets.UTF_8
)

Storage contract explicit।


Missing vs Failure

Missing file:

NoSuchFileException

becomes:

null

according to repository contract।

Other I/O failure becomes:

CourseRepositoryException

Corrupted Data

Example:

priceInPaisa=hello

does not become:

Course not found

It becomes repository failure।


Atomic Replacement

Repository first writes:

Temporary file

Then moves it over:

Actual course file

This reduces partial replacement risk।


Part 7: Application Demo

Main.java

package io.liveklass;

import io.liveklass.course.Course;
import io.liveklass.course.CourseCode;
import io.liveklass.course.CourseStatus;
import io.liveklass.repository.CourseRepository;
import io.liveklass.repository.FileCourseRepository;

import java.nio.file.Path;

public class Main {

    public static void main(
            String[] args
    ) {
        CourseRepository repository =
                new FileCourseRepository(
                        Path.of(
                                "data",
                                "courses"
                        )
                );

        Course java =
                new Course(
                        new CourseCode(
                                "JAVA-OOP"
                        ),
                        "Java and OOP Foundation",
                        499_000L,
                        CourseStatus.DRAFT
                );

        Course backend =
                new Course(
                        new CourseCode(
                                "BACKEND"
                        ),
                        "Backend Development",
                        799_000L,
                        CourseStatus.REVIEW
                );

        repository.save(
                java
        );

        repository.save(
                backend
        );

        Course loaded =
                repository.findByCode(
                        new CourseCode(
                                "java-oop"
                        )
                );

        System.out.println(
                "Loaded:"
        );

        System.out.println(
                loaded
        );

        System.out.println();
        System.out.println(
                "All courses:"
        );

        repository.findAll()
                .forEach(
                        System.out::println
                );

        repository.backupTo(
                Path.of(
                        "backup",
                        "courses"
                )
        );

        boolean deleted =
                repository.deleteByCode(
                        new CourseCode(
                                "BACKEND"
                        )
                );

        System.out.println();
        System.out.println(
                "Backend deleted: "
                + deleted
        );
    }
}

Expected Storage

After save:

data/
└── courses/
    ├── backend.properties
    └── java-oop.properties

Backup:

backup/
└── courses/
    ├── backend.properties
    └── java-oop.properties

After deleting BACKEND:

data/
└── courses/
    └── java-oop.properties

The backup still contains the earlier copy।


Assessment Part 1: Choose the Correct API

Choose the best API for each case।

Question 1

Read an entire 30 KB Markdown lesson।

Options:

Files.readString()
Files.lines()
InputStream

Answer

Files.readString()

The file is small and the entire content is needed।


Question 2

Search a 5 GB log file for the first matching line।

Answer

Files.lines()

or:

BufferedReader

Streaming avoids loading the whole file।


Question 3

Copy a 500 MB PDF exactly।

Answer

Files.copy()

if no custom processing is required।

Otherwise use:

InputStream
OutputStream

Question 4

Generate one million lines incrementally।

Answer

BufferedWriter

Question 5

List direct children of a directory।

Answer

Files.list()

Question 6

Search every nested file under a directory tree।

Answer

Files.walk()

Assessment Part 2: Find the Bug

Problem 1

String path =
        "data/"
        + courseCode
        + "/course.properties";

Problem

Manual path construction।

Better:

Path path =
        Path.of(
                "data",
                courseCode,
                "course.properties"
        );

or use:

resolve()

Problem 2

if (
        Files.exists(
                path
        )
) {
    Files.delete(
            path
    );
}

Problem

Check-then-act race।

Better:

Files.deleteIfExists(
        path
);

Problem 3

Stream<Path> paths =
        Files.walk(
                root
        );

paths.forEach(
        System.out::println
);

Problem

The resource-backed stream is not closed।

Better:

try (
        Stream<Path> paths =
                Files.walk(
                        root
                )
) {
    paths.forEach(
            System.out::println
    );
}

Problem 4

Files.readString(
        Path.of(
                "video.mp4"
        )
);

Problem

Video is binary data।

Use byte-oriented APIs।


Problem 5

try (
        InputStream input =
                Files.newInputStream(
                        path
                )
) {
    return input;
}

Problem

Returned stream is already closed।


Problem 6

catch (
        IOException exception
) {
    return null;
}

inside every repository operation।

Problem

This makes:

Not found
Permission denied
Corrupted storage
Disk failure

look identical।


Assessment Part 3: Path Safety

Suppose:

Path base =
        Path.of(
                "/data/courses"
        )
        .toAbsolutePath()
        .normalize();

User provides:

../../secret.txt

A safer pattern:

Path target =
        base.resolve(
                userInput
        )
        .normalize();

if (
        !target.startsWith(
                base
        )
) {
    throw new IllegalArgumentException(
            "Path escapes storage root."
    );
}

Why Path Validation Matters

Without validation:

/data/courses
+
../../secret.txt

can resolve outside the intended storage root।

External input should not receive uncontrolled file-system access।


Assessment Part 4: Resource Ownership

Consider:

public void process(
        InputStream input
) throws IOException {
}

Caller created the stream।

Should process() close it?

Default answer:

No

unless method contract explicitly transfers ownership।

A useful rule:

Who opens the resource usually owns the resource.

Assessment Part 5: Serialization

Why does the repository write:

course.getStatus()
        .name()

instead of:

course.getStatus()
        .ordinal()

Because:

name() → Meaningful stable identifier
ordinal() → Depends on declaration order

Changing enum order could corrupt stored meaning if ordinal is persisted।


Assessment Part 6: Missing vs Broken Data

Suppose:

java-oop.properties

does not exist।

Possible repository result:

null

according to our contract।

Now suppose it exists but contains:

priceInPaisa=banana

Correct result:

CourseRepositoryException

The entity is not missing।

Its stored representation is invalid।


Assessment Part 7: Atomic Save

Why use:

Temporary file
↓
Write new content
↓
Move over target

instead of directly overwriting important content?

Because new data is fully written before the target is replaced।

This reduces the chance of exposing an incomplete target file।


Does Atomic Move Solve Concurrency?

No।

Suppose:

Sakib loads version 1
Jalisa loads version 1

Sakib changes title
Jalisa changes status

Sakib saves
Jalisa saves afterward

Jalisa's file may overwrite Sakib's title change।

Atomic replacement ensures each individual file replacement is complete।

It does not detect stale writers।


File Storage vs Database Assessment

Choose the more appropriate storage।

Case 1

A small command-line tool storing five settings।

Answer

File storage can be enough।


Case 2

Static Markdown lessons deployed with an application।

Answer

File or object storage can be appropriate।


Case 3

Millions of learner enrollments with concurrent writes।

Answer

Database।


Case 4

Course images and videos।

Answer

File/object storage is appropriate for the media itself।

Metadata may live in a database।


Case 5

Payments requiring transactions and uniqueness guarantees।

Answer

Database-backed transactional persistence।


Independent Practice 1: Course Content Storage

Create:

CourseContentStorage

Contract:

void write(
        CourseCode courseCode,
        String lessonName,
        String content
);

String read(
        CourseCode courseCode,
        String lessonName
);

boolean delete(
        CourseCode courseCode,
        String lessonName
);

Requirements:

  • UTF-8
  • Safe base path
  • Parent directory creation
  • Exception translation
  • Missing content distinguished from storage failure

Independent Practice 2: Recursive Backup

Implement:

void backup(
        Path source,
        Path target
)

Requirements:

  • Recursively walk source
  • Preserve relative structure
  • Create directories
  • Copy files
  • Use try-with-resources
  • Translate IOException

Independent Practice 3: Large Export

Generate:

1,000,000 course records

as text।

Do not create one huge String

Use:

BufferedWriter

and write incrementally।


Independent Practice 4: Safe Cleanup

Implement:

void deleteCourseDirectory(
        Path storageRoot,
        CourseCode courseCode
)

Requirements:

  • Resolve under storage root
  • Normalize
  • Verify startsWith(storageRoot)
  • Reject deleting the storage root itself
  • Walk recursively
  • Delete children before parents

Predict the Result

Question 1

Path path =
        Path.of(
                "courses",
                "java",
                "..",
                "backend"
        )
        .normalize();

System.out.println(
        path
);

Answer

Conceptually:

courses/backend

Question 2

Files.createDirectories(
        existingDirectory
);

Does it fail simply because the directory exists?

Answer

Normally no।


Question 3

Files.deleteIfExists(
        missingPath
);

What does it return?

Answer

false

Question 4

InputStream input =
        Files.newInputStream(
                path
        );

int result =
        input.read();

What does:

-1

mean?

Answer

End of stream।


Question 5

Does:

Files.copy(
        directory,
        target
);

automatically copy all descendants?

Answer

No।

Recursive directory copy requires traversal।


Question 6

What is wrong with:

Files.readAllLines(
        hugeFile
);

for a 20 GB file?

Answer

It attempts to represent the whole file as an in-memory list।

Use streaming instead।


True or False

  1. Creating a Path reads the file.
  2. Files.readString() loads the complete text.
  3. Files.lines() should usually be closed.
  4. BufferedWriter can support incremental output.
  5. InputStream is intended for text only.
  6. Files.createDirectories() can create missing parents.
  7. Files.walk() is recursive.
  8. Files.list() is recursive.
  9. deleteIfExists() is useful for cleanup.
  10. Atomic move always works on every file system.
  11. File repository can hide IOException from business code.
  12. Missing data and corrupted data are the same condition.
  13. Enum ordinal is a strong persistence format.
  14. Atomic replacement prevents all lost updates.
  15. File storage is always simpler than a database.

Answers

1. False
2. True
3. True
4. True
5. False
6. True
7. True
8. False
9. True
10. False
11. True
12. False
13. False
14. False
15. False

Final Design Challenge

Design a small storage system for course lessons।

Each course has:

courseCode

Each lesson has:

lessonId
title
Markdown content

Storage layout:

content/
└── java-oop/
    ├── lesson-1.md
    ├── lesson-2.md
    └── lesson-3.md

Your design must answer:

  1. Which layer builds paths?
  2. How is path traversal prevented?
  3. Which encoding is used?
  4. How is content read?
  5. How is content updated safely?
  6. How are missing files represented?
  7. Which exceptions leave the storage layer?
  8. Who closes streams?
  9. How are all course lessons listed?
  10. How would a backup be created?

Suggested Design

Path Ownership

Storage adapter builds paths।

Domain code only provides:

CourseCode
LessonId

Path Safety

Use:

resolve()
normalize()
startsWith()

against a fixed storage root।


Encoding

Use:

StandardCharsets.UTF_8

for all Markdown text।


Reading

Typical lesson:

Files.readString()

For very large text:

BufferedReader

or:

Files.lines()

Updating

Use:

Write temporary file
Move over target

for important full-file replacements।


Missing Content

Represent intentionally as:

null
Optional
Custom not-found exception

depending on repository contract।

Do not use the same representation for general I/O failure।


Exception Boundary

Translate:

IOException

into:

CourseContentStorageException

while preserving the cause।


Resource Ownership

The component that opens a stream should normally close it।

Use:

try-with-resources

Listing Lessons

Use:

Files.list()

for direct lesson files।

Use:

Files.walk()

if nested module directories are supported।


Backup

Walk the source tree, calculate:

source.relativize(
        current
)

and reproduce that path under the backup root।


Evaluation Rubric

Score each area:

0 = Missing
1 = Partially correct
2 = Correct
AreaScore
Path usage/2
Safe path resolution/2
UTF-8 handling/2
Correct read API choice/2
Correct write API choice/2
Try-with-resources/2
Byte vs character I/O/2
Directory operations/2
Recursive traversal/2
Repository abstraction/2
Exception translation/2
Missing vs failure distinction/2
Atomic replacement/2
Resource ownership/2
File-storage limitations understood/2

Maximum:

30

Interpretation:

26–30 → Strong Java I/O foundation
21–25 → Good understanding
15–20 → Review resource and storage design
Below 15 → Rebuild the practice project

Module Completion Checklist

Before completing Module 6, verify that you can:

  • Create Path objects
  • Distinguish relative and absolute paths
  • Use resolve() and normalize()
  • Validate paths against a storage root
  • Check regular files and directories
  • Read text with Files.readString()
  • Read lines with readAllLines()
  • Stream large files with Files.lines()
  • Use BufferedReader
  • Write text with Files.writeString()
  • Append with StandardOpenOption.APPEND
  • Create nested directories
  • Use InputStream and OutputStream
  • Distinguish binary and text I/O
  • Use BufferedWriter for incremental output
  • Use try-with-resources
  • Explain AutoCloseable
  • Explain suppressed exceptions
  • Copy, move, rename, and delete files
  • Use Files.list() and Files.walk()
  • Perform safe recursive cleanup
  • Build a file-backed repository
  • Translate low-level I/O failures
  • Distinguish missing from corrupted storage
  • Explain file-storage concurrency limitations

Module Summary

এই module-এ আমরা শিখেছি:

  • Path file-system location represent করে
  • Files common file-system operations provide করে
  • Relative এবং absolute paths different semantics রাখে
  • resolve() path composition simplify করে
  • normalize() redundant path components remove করে
  • Untrusted paths storage root-এর মধ্যে validate করা উচিত
  • Files.readString() small full-text reads-এর জন্য useful
  • Files.readAllLines() small line-based files-এর জন্য useful
  • Files.lines() large files stream করতে পারে
  • BufferedReader controlled text parsing support করে
  • Files.writeString() text create বা replace করতে পারে
  • APPEND existing content preserve করে
  • CREATE_NEW duplicate creation reject করতে পারে
  • BufferedWriter large output incrementally লিখতে useful
  • InputStream এবং OutputStream raw byte I/O
  • Reader এবং Writer character I/O
  • Binary data text হিসেবে process করা উচিত নয়
  • Try-with-resources automatic cleanup নিশ্চিত করে
  • AutoCloseable resource lifecycle contract provide করে
  • Multiple resources reverse order-এ close হয়
  • Cleanup failures suppressed exception হিসেবে preserved হতে পারে
  • Files.copy() এবং Files.move() high-level operations provide করে
  • Rename সাধারণত move operation
  • Files.list() direct children return করে
  • Files.walk() recursive traversal support করে
  • Recursive delete children-first হওয়া উচিত
  • File-system pre-checks concurrency guarantee করে না
  • Repository storage implementation domain logic থেকে hide করতে পারে
  • Missing entity এবং storage failure আলাদা রাখা উচিত
  • UTF-8 encoding explicitly define করা উচিত
  • Temporary-file replacement safer full-file update support করতে পারে
  • Atomic replacement logical lost update prevent করে না
  • File storage useful হলেও transactions, querying, and concurrent updates-এর ক্ষেত্রে database অনেক stronger

Module Complete

Module 6-এর main design principle:

Use the simplest correct I/O API,
keep resource ownership explicit,
and keep storage details behind a clear boundary.

File handling শুধু file read/write করা নয়।

Strong I/O code বুঝে:

What is being stored?
How large is it?
Who owns the resource?
What can fail?
What must remain atomic?
Which layer should know the file system?

এই thinking production-quality storage code-এর foundation।