EBook.java
package com.university.bookstore.model;
import java.util.Objects;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* Represents an electronic book (e-book) in the bookstore system.
* Extends Material and implements Media interface to demonstrate
* multiple inheritance through interfaces.
*
* <p>EBooks support various file formats, DRM protection, and
* provide reading time estimation based on word count.</p>
*
* @author Navid Mohaghegh
* @version 2.0
* @since 2024-09-15
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class EBook extends Material implements Media {
private static final Set<String> VALID_FORMATS = Set.of("PDF", "EPUB", "MOBI");
private static final int WORDS_PER_MINUTE = 250;
private static final double DRM_FREE_DISCOUNT = 0.15;
private final String author;
private final String fileFormat;
private final double fileSize; // in MB
private final boolean drmEnabled;
private final int wordCount;
private final MediaQuality quality;
/**
* Constructs a new EBook with validation.
*
* @param id unique identifier
* @param title the book title
* @param author the author name
* @param price the price in dollars
* @param year the publication year
* @param fileFormat the file format (PDF, EPUB, or MOBI)
* @param fileSize the file size in MB
* @param drmEnabled whether DRM is enabled
* @param wordCount the number of words in the book
* @param quality the media quality
* @throws IllegalArgumentException if validation fails
*/
@JsonCreator
public EBook(@JsonProperty("id") String id,
@JsonProperty("title") String title,
@JsonProperty("author") String author,
@JsonProperty("price") double price,
@JsonProperty("year") int year,
@JsonProperty("fileFormat") String fileFormat,
@JsonProperty("fileSize") double fileSize,
@JsonProperty("drmEnabled") boolean drmEnabled,
@JsonProperty("wordCount") int wordCount,
@JsonProperty("quality") MediaQuality quality) {
super(id, title, price, year, MaterialType.E_BOOK);
this.author = validateStringField(author, "Author");
this.fileFormat = validateFileFormat(fileFormat);
this.fileSize = validateFileSize(fileSize);
this.drmEnabled = drmEnabled;
this.wordCount = validateWordCount(wordCount);
this.quality = Objects.requireNonNull(quality, "Media quality cannot be null");
}
/**
* Validates that the file format is supported.
*
* @param format the file format to validate
* @return the validated format
* @throws IllegalArgumentException if format is not supported
*/
private String validateFileFormat(String format) {
if (format == null) {
throw new NullPointerException("File format cannot be null");
}
String upperFormat = format.trim().toUpperCase();
if (!VALID_FORMATS.contains(upperFormat)) {
throw new IllegalArgumentException(
String.format("Unsupported file format: %s. Supported formats: %s",
format, VALID_FORMATS));
}
return upperFormat;
}
/**
* Validates that the file size is positive.
*
* @param size the file size to validate
* @return the validated size
* @throws IllegalArgumentException if size is not positive
*/
private double validateFileSize(double size) {
if (size <= 0.0) {
throw new IllegalArgumentException(
String.format("File size must be positive. Provided: %.2f MB", size));
}
if (Double.isNaN(size) || Double.isInfinite(size)) {
throw new IllegalArgumentException(
String.format("File size must be a valid number. Provided: %.2f MB", size));
}
return size;
}
/**
* Validates that the word count is non-negative.
*
* @param count the word count to validate
* @return the validated count
* @throws IllegalArgumentException if count is negative
*/
private int validateWordCount(int count) {
if (count < 0) {
throw new IllegalArgumentException(
String.format("Word count cannot be negative. Provided: %d", count));
}
return count;
}
@Override
public String getCreator() {
return author;
}
/**
* Gets the author of the e-book.
* @return the author
*/
public String getAuthor() {
return author;
}
@Override
public String getDisplayInfo() {
return String.format("EBook: %s by %s (%s, %.1f MB, %s, %d words)",
getTitle(), author, fileFormat, fileSize,
drmEnabled ? "DRM" : "DRM-free", wordCount);
}
@Override
public double getDiscountRate() {
// DRM-free books get a 15% discount
return drmEnabled ? 0.0 : DRM_FREE_DISCOUNT;
}
/**
* Calculates estimated reading time in minutes based on average reading speed.
*
* @return estimated reading time in minutes
*/
public int getReadingTimeMinutes() {
return wordCount / WORDS_PER_MINUTE;
}
/**
* Gets the file format of the ebook.
*
* @return the file format
*/
public String getFileFormat() {
return fileFormat;
}
/**
* Gets the file size in MB.
*
* @return the file size
*/
public double getFileSize() {
return fileSize;
}
/**
* Checks if DRM is enabled.
*
* @return true if DRM is enabled
*/
public boolean isDrmEnabled() {
return drmEnabled;
}
/**
* Gets the word count of the book.
*
* @return the word count
*/
public int getWordCount() {
return wordCount;
}
@Override
public MediaQuality getQuality() {
return quality;
}
public String getDescription() {
return String.format("Digital %s format book by %s. %s, %.1f MB. Estimated reading time: %d minutes.",
fileFormat, author,
drmEnabled ? "DRM protected" : "DRM-free",
fileSize,
getReadingTimeMinutes());
}
// Media interface implementation
@Override
public int getDuration() {
return getReadingTimeMinutes();
}
@Override
public String getFormat() {
return fileFormat;
}
@Override
public boolean isStreamingOnly() {
return false; // E-books are downloadable, not streaming
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof EBook)) return false;
if (!super.equals(obj)) return false;
EBook other = (EBook) obj;
return Double.compare(other.fileSize, fileSize) == 0 &&
drmEnabled == other.drmEnabled &&
wordCount == other.wordCount &&
Objects.equals(author, other.author) &&
Objects.equals(fileFormat, other.fileFormat) &&
quality == other.quality;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), author, fileFormat, fileSize,
drmEnabled, wordCount, quality);
}
@Override
public String toString() {
return String.format("EBook[ID=%s, Title='%s', Author='%s', Price=$%.2f, Year=%d, " +
"Format=%s, Size=%.1fMB, DRM=%s, Words=%d, Quality=%s]",
getId(), getTitle(), author, getPrice(), getYear(),
fileFormat, fileSize, drmEnabled, wordCount, quality);
}
}