Java Basics  «Prev 

Module 1 Project - Source Code and Submission Guide (Java 17)


What this project is

You’ll build a small, testable Java 17 app that demonstrates packages, interfaces, encapsulation, and access control (Module 1 core skills). You’ll also practice directory structure, basic testing, and a minimal CLI (or `main`) entry point.

Project goals (why this matters)


Starter code and structure
Download: *{Place your starter ZIP link here, e.g. `/downloads/module1-project-starter.zip`}*
Expected layout (Maven-esque, works for IntelliJ/Eclipse/VS Code):
 
project/
  README.md
  pom.xml (or build.gradle)
  src/
    main/
      java/
        com/yourname/oop/module1/
          App.java
          model/
          service/
          repo/
    test/
      java/
        com/yourname/oop/module1/
          AppTest.java


Set your base package: com.yourname.oop.module1 (replace yourname)

Requirements (functional and technical)
  1. Domain model (minimal but real):
    • model/Student (id, name, email) - fields private, validate in constructor/setters.
    • model/Course (code, title).
    • model/Enrollment (studentId, courseCode, status).
  2. Repository contract and impl:
    • 
      // repo/Repository.java
      package com.yourname.oop.module1.repo;
      import java.util.*;
      
      public interface Repository<T, ID> {
          T save(T entity);
          Optional<T> findById(ID id);
          List<T> findAll();
          boolean deleteById(ID id);
      }
                      
    • 
      // repo/InMemoryRepository.java
      package com.yourname.oop.module1.repo;
      import java.util.*;
      import java.util.concurrent.ConcurrentHashMap;
      
      public class InMemoryRepository<T, ID> implements Repository<T, ID> {
          private final Map<ID, T> store = new ConcurrentHashMap<>();
          private final java.util.function.Function<T, ID> idExtractor;
      
          public InMemoryRepository(java.util.function.Function<T, ID> idExtractor) {
              this.idExtractor = Objects.requireNonNull(idExtractor);
          }
      
          @Override public T save(T entity) {
              ID id = idExtractor.apply(entity);
              if (id == null) throw new IllegalArgumentException("Entity id cannot be null");
              store.put(id, entity);
              return entity;
          }
          @Override public Optional<T> findById(ID id) { return Optional.ofNullable(store.get(id)); }
          @Override public List<T> findAll() { return new ArrayList<>(store.values()); }
          @Override public boolean deleteById(ID id) { return store.remove(id) != null; }
      }
                      
  3. Service layer (business logic):
    • service/EnrollmentService that uses repositories to enroll a student, list enrollments, drop course, etc.
    • Enforce simple rules (e.g., no duplicate enrollment).
  4. Entry point (App.java): minimal CLI / demo that:
    • creates repos, seeds 1–2 students and courses, enrolls, prints results.
    • No fancy I/O needed; System.out.println is fine.

Example (concise demo)

// App.java
package com.yourname.oop.module1;

import com.yourname.oop.module1.model.*;
import com.yourname.oop.module1.repo.*;
import java.util.*;

public class App {
    public static void main(String[] args) {
        Repository<Student, String> students =
            new InMemoryRepository<>(Student::id);
        Repository<Course, String> courses =
            new InMemoryRepository<>(Course::code);

        students.save(new Student("s-1", "Ava", "ava@example.com"));
        courses.save(new Course("CS101", "Intro to CS"));

        System.out.println("Students: " + students.findAll().size());
        System.out.println("Courses: " + courses.findAll().size());
    }
}

// model/Student.java
package com.yourname.oop.module1.model;
import java.util.Objects;

public final class Student {
    private final String id;
    private String name;
    private String email;

    public Student(String id, String name, String email) {
        this.id = Objects.requireNonNull(id);
        setName(name);
        setEmail(email);
    }
    public String id() { return id; }
    public String name() { return name; }
    public String email() { return email; }

    public void setName(String name) {
        if (name == null || name.isBlank()) throw new IllegalArgumentException("name");
        this.name = name.trim();
    }
    public void setEmail(String email) {
        if (email == null || !email.contains("@")) throw new IllegalArgumentException("email");
        this.email = email.trim();
    }
}



Tests (JUnit 5)


// AppTest.java
package com.yourname.oop.module1;

import com.yourname.oop.module1.model.Student;
import com.yourname.oop.module1.repo.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class AppTest {
    @Test
    void canSaveAndFindStudent() {
        Repository<Student, String> students = new InMemoryRepository<>(Student::id);
        Student s = new Student("s-1", "Ava", "ava@example.com");
        students.save(s);
        assertTrue(students.findById("s-1").isPresent());
    }
}
Build/Run:
Common mistakes (and fixes)
Submission checklist
Quick check (answers below)
  1. Why define a Repository<T,ID> interface?
  2. Where should validation live-constructor, setters, or service?
  3. When is package-private preferable to public?

Answers:
  1. To program to a contract, enable multiple impls (e.g., in-memory vs DB).
  2. Validate invariants in constructors/setters; cross-entity rules in services.
  3. When the API is internal to a package and you don’t want to expose it publicly.

Next steps