# MaterialFactory.java

    package com.university.bookstore.factory;

    import java.util.Map;

    import com.university.bookstore.model.AudioBook;
    import com.university.bookstore.model.EBook;
    import com.university.bookstore.model.Magazine;
    import com.university.bookstore.model.Material;
    import com.university.bookstore.model.Media;
    import com.university.bookstore.model.PrintedBook;
    import com.university.bookstore.model.VideoMaterial;

    /**
     * Factory class for creating Material instances.
     * Demonstrates the Factory pattern by providing a centralized
     * way to create different types of materials without exposing
     * their constructors directly.
     *
     * <p>This factory supports creating all material types including
     * the new EBook class, with proper validation and error handling.</p>
     *
     * @author Navid Mohaghegh
     * @version 2.0
     * @since 2024-09-15
     */
    public class MaterialFactory {

        /**
         * Creates a Material instance based on the specified type and properties.
         *
         * @param type the material type
         * @param properties map of properties required for the material
         * @return the created Material instance
         * @throws IllegalArgumentException if type is null or properties are invalid
         * @throws NullPointerException if required properties are missing
         */
        public static Material createMaterial(String type, Map<String, Object> properties) {
            if (type == null) {
                throw new NullPointerException("Material type cannot be null");
            }
            if (properties == null) {
                throw new NullPointerException("Properties cannot be null");
            }

            String normalizedType = type.trim().toUpperCase();

            switch (normalizedType) {
                case "BOOK":
                case "PRINTED_BOOK":
                    return createPrintedBook(properties);
                case "MAGAZINE":
                    return createMagazine(properties);
                case "AUDIO_BOOK":
                case "AUDIOBOOK":
                    return createAudioBook(properties);
                case "VIDEO":
                case "VIDEO_MATERIAL":
                    return createVideoMaterial(properties);
                case "EBOOK":
                case "E_BOOK":
                    return createEBook(properties);
                default:
                    throw new IllegalArgumentException("Unsupported material type: " + type);
            }
        }

        /**
         * Creates a PrintedBook instance.
         */
        private static PrintedBook createPrintedBook(Map<String, Object> properties) {
            String isbn = getRequiredString(properties, "isbn");
            String title = getRequiredString(properties, "title");
            String author = getRequiredString(properties, "author");
            double price = getRequiredDouble(properties, "price");
            int year = getRequiredInteger(properties, "year");
            int pages = getRequiredInteger(properties, "pages");
            String publisher = getOptionalString(properties, "publisher", "Unknown");
            boolean hardcover = getOptionalBoolean(properties, "hardcover", false);

            return new PrintedBook(isbn, title, author, price, year, pages, publisher, hardcover);
        }

        /**
         * Creates a Magazine instance.
         */
        private static Magazine createMagazine(Map<String, Object> properties) {
            String issn = getRequiredString(properties, "issn");
            String title = getRequiredString(properties, "title");
            String publisher = getRequiredString(properties, "publisher");
            double price = getRequiredDouble(properties, "price");
            int year = getRequiredInteger(properties, "year");
            int issue = getRequiredInteger(properties, "issue");
            String frequency = getOptionalString(properties, "frequency", "Monthly");
            String category = getOptionalString(properties, "category", "General");

            return new Magazine(issn, title, publisher, price, year, issue, frequency, category);
        }

        /**
         * Creates an AudioBook instance.
         * Note: This is a simplified version for demonstration purposes.
         */
        private static AudioBook createAudioBook(Map<String, Object> properties) {
            String isbn = getRequiredString(properties, "isbn");
            String title = getRequiredString(properties, "title");
            String author = getRequiredString(properties, "author");
            String narrator = getRequiredString(properties, "narrator");
            double price = getRequiredDouble(properties, "price");
            int year = getRequiredInteger(properties, "year");
            int duration = getRequiredInteger(properties, "duration");
            String format = getOptionalString(properties, "format", "MP3");
            double fileSize = getOptionalDouble(properties, "fileSize", 100.0);
            Media.MediaQuality quality = getRequiredMediaQuality(properties, "quality");
            String language = getOptionalString(properties, "language", "English");
            boolean unabridged = getOptionalBoolean(properties, "unabridged", true);

            return new AudioBook(isbn, title, author, narrator, price, year, duration,
                               format, fileSize, quality, language, unabridged);
        }

        /**
         * Creates a VideoMaterial instance.
         * Note: This is a simplified version for demonstration purposes.
         */
        private static VideoMaterial createVideoMaterial(Map<String, Object> properties) {
            String id = getRequiredString(properties, "id");
            String title = getRequiredString(properties, "title");
            String director = getRequiredString(properties, "director");
            double price = getRequiredDouble(properties, "price");
            int year = getRequiredInteger(properties, "year");
            int duration = getRequiredInteger(properties, "duration");
            String format = getOptionalString(properties, "format", "MP4");
            double fileSize = getOptionalDouble(properties, "fileSize", 1000.0);
            Media.MediaQuality quality = getRequiredMediaQuality(properties, "quality");
            VideoMaterial.VideoType videoType = getRequiredVideoType(properties, "videoType");
            String rating = getOptionalString(properties, "rating", "PG");
            java.util.List<String> cast = getOptionalStringList(properties, "cast");
            boolean subtitlesAvailable = getOptionalBoolean(properties, "subtitlesAvailable", false);
            String aspectRatio = getOptionalString(properties, "aspectRatio", "16:9");

            return new VideoMaterial(id, title, director, price, year, duration, format,
                                   fileSize, quality, videoType, rating, cast,
                                   subtitlesAvailable, aspectRatio);
        }

        /**
         * Creates an EBook instance.
         */
        private static EBook createEBook(Map<String, Object> properties) {
            String id = getRequiredString(properties, "id");
            String title = getRequiredString(properties, "title");
            String author = getRequiredString(properties, "author");
            double price = getRequiredDouble(properties, "price");
            int year = getRequiredInteger(properties, "year");
            String fileFormat = getRequiredString(properties, "fileFormat");
            double fileSize = getRequiredDouble(properties, "fileSize");
            boolean drmEnabled = getRequiredBoolean(properties, "drmEnabled");
            int wordCount = getRequiredInteger(properties, "wordCount");
            Media.MediaQuality quality = getRequiredMediaQuality(properties, "quality");

            return new EBook(id, title, author, price, year, fileFormat, fileSize,
                            drmEnabled, wordCount, quality);
        }

        // Helper methods for extracting and validating properties

        private static String getRequiredString(Map<String, Object> properties, String key) {
            Object value = properties.get(key);
            if (value == null) {
                throw new NullPointerException("Required property '" + key + "' is missing");
            }
            if (!(value instanceof String)) {
                throw new IllegalArgumentException("Property '" + key + "' must be a String, got: " + value.getClass().getSimpleName());
            }
            String str = (String) value;
            if (str.trim().isEmpty()) {
                throw new IllegalArgumentException("Property '" + key + "' cannot be empty");
            }
            return str.trim();
        }

        private static double getRequiredDouble(Map<String, Object> properties, String key) {
            Object value = properties.get(key);
            if (value == null) {
                throw new NullPointerException("Required property '" + key + "' is missing");
            }
            if (value instanceof Number) {
                return ((Number) value).doubleValue();
            }
            if (value instanceof String) {
                try {
                    return Double.parseDouble((String) value);
                } catch (NumberFormatException e) {
                    throw new IllegalArgumentException("Property '" + key + "' must be a valid number, got: " + value);
                }
            }
            throw new IllegalArgumentException("Property '" + key + "' must be a number, got: " + value.getClass().getSimpleName());
        }

        private static int getRequiredInteger(Map<String, Object> properties, String key) {
            Object value = properties.get(key);
            if (value == null) {
                throw new NullPointerException("Required property '" + key + "' is missing");
            }
            if (value instanceof Number) {
                return ((Number) value).intValue();
            }
            if (value instanceof String) {
                try {
                    return Integer.parseInt((String) value);
                } catch (NumberFormatException e) {
                    throw new IllegalArgumentException("Property '" + key + "' must be a valid integer, got: " + value);
                }
            }
            throw new IllegalArgumentException("Property '" + key + "' must be an integer, got: " + value.getClass().getSimpleName());
        }

        private static boolean getRequiredBoolean(Map<String, Object> properties, String key) {
            Object value = properties.get(key);
            if (value == null) {
                throw new NullPointerException("Required property '" + key + "' is missing");
            }
            if (value instanceof Boolean) {
                return (Boolean) value;
            }
            if (value instanceof String) {
                String str = ((String) value).trim().toLowerCase();
                if ("true".equals(str) || "1".equals(str) || "yes".equals(str)) {
                    return true;
                }
                if ("false".equals(str) || "0".equals(str) || "no".equals(str)) {
                    return false;
                }
                throw new IllegalArgumentException("Property '" + key + "' must be a valid boolean, got: " + value);
            }
            throw new IllegalArgumentException("Property '" + key + "' must be a boolean, got: " + value.getClass().getSimpleName());
        }

        private static Media.MediaQuality getRequiredMediaQuality(Map<String, Object> properties, String key) {
            Object value = properties.get(key);
            if (value == null) {
                throw new NullPointerException("Required property '" + key + "' is missing");
            }
            if (value instanceof Media.MediaQuality) {
                return (Media.MediaQuality) value;
            }
            if (value instanceof String) {
                try {
                    return Media.MediaQuality.valueOf(((String) value).trim().toUpperCase());
                } catch (IllegalArgumentException e) {
                    throw new IllegalArgumentException("Property '" + key + "' must be a valid MediaQuality, got: " + value);
                }
            }
            throw new IllegalArgumentException("Property '" + key + "' must be a MediaQuality, got: " + value.getClass().getSimpleName());
        }

        private static String getOptionalString(Map<String, Object> properties, String key, String defaultValue) {
            Object value = properties.get(key);
            if (value == null) {
                return defaultValue;
            }
            if (!(value instanceof String)) {
                throw new IllegalArgumentException("Property '" + key + "' must be a String, got: " + value.getClass().getSimpleName());
            }
            String str = (String) value;
            return str.trim().isEmpty() ? defaultValue : str.trim();
        }

        private static boolean getOptionalBoolean(Map<String, Object> properties, String key, boolean defaultValue) {
            Object value = properties.get(key);
            if (value == null) {
                return defaultValue;
            }
            if (value instanceof Boolean) {
                return (Boolean) value;
            }
            if (value instanceof String) {
                String str = ((String) value).trim().toLowerCase();
                if ("true".equals(str) || "1".equals(str) || "yes".equals(str)) {
                    return true;
                }
                if ("false".equals(str) || "0".equals(str) || "no".equals(str)) {
                    return false;
                }
                throw new IllegalArgumentException("Property '" + key + "' must be a valid boolean, got: " + value);
            }
            throw new IllegalArgumentException("Property '" + key + "' must be a boolean, got: " + value.getClass().getSimpleName());
        }

        private static double getOptionalDouble(Map<String, Object> properties, String key, double defaultValue) {
            Object value = properties.get(key);
            if (value == null) {
                return defaultValue;
            }
            if (value instanceof Number) {
                return ((Number) value).doubleValue();
            }
            if (value instanceof String) {
                try {
                    return Double.parseDouble((String) value);
                } catch (NumberFormatException e) {
                    throw new IllegalArgumentException("Property '" + key + "' must be a valid number, got: " + value);
                }
            }
            throw new IllegalArgumentException("Property '" + key + "' must be a number, got: " + value.getClass().getSimpleName());
        }

        private static VideoMaterial.VideoType getRequiredVideoType(Map<String, Object> properties, String key) {
            Object value = properties.get(key);
            if (value == null) {
                throw new NullPointerException("Required property '" + key + "' is missing");
            }
            if (value instanceof VideoMaterial.VideoType) {
                return (VideoMaterial.VideoType) value;
            }
            if (value instanceof String) {
                try {
                    return VideoMaterial.VideoType.valueOf(((String) value).trim().toUpperCase());
                } catch (IllegalArgumentException e) {
                    throw new IllegalArgumentException("Property '" + key + "' must be a valid VideoType, got: " + value);
                }
            }
            throw new IllegalArgumentException("Property '" + key + "' must be a VideoType, got: " + value.getClass().getSimpleName());
        }

        @SuppressWarnings("unchecked")
        private static java.util.List<String> getOptionalStringList(Map<String, Object> properties, String key) {
            Object value = properties.get(key);
            if (value == null) {
                return java.util.Arrays.asList();
            }
            if (value instanceof java.util.List) {
                return (java.util.List<String>) value;
            }
            if (value instanceof String) {
                String str = (String) value;
                if (str.trim().isEmpty()) {
                    return java.util.Arrays.asList();
                }
                return java.util.Arrays.asList(str.split(","));
            }
            throw new IllegalArgumentException("Property '" + key + "' must be a List<String> or String, got: " + value.getClass().getSimpleName());
        }
    }