# BookArrayUtils.java

    package com.university.bookstore.utils;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.TreeMap;
    import java.util.stream.Stream;

    import com.university.bookstore.model.Book;

    /**
     * Utility class for array-based operations on Book objects.
     *
     * <p>This class provides static methods for manipulating and analyzing
     * arrays of books, demonstrating array operations without using ArrayList.</p>
     *
     * <p>All methods handle null arrays and null elements gracefully.</p>
     *
     * @author Navid Mohaghegh
     * @version 1.0
     * @since 2024-09-15
     */
    public final class BookArrayUtils {

        /**
         * Private constructor to prevent instantiation.
         */
        private BookArrayUtils() {
            throw new UnsupportedOperationException("Utility class cannot be instantiated");
        }

        /**
         * Counts books published before a given year.
         *
         * @param books array of books (may be null or contain nulls)
         * @param yearCutoff the cutoff year (exclusive)
         * @return count of books published before the cutoff year
         */
        public static int countBeforeYear(Book[] books, int yearCutoff) {
            if (books == null) {
                return 0;
            }

            int count = 0;
            for (Book book : books) {
                if (book != null && book.getYear() < yearCutoff) {
                    count++;
                }
            }
            return count;
        }

        /**
         * Counts books by a specific author (case-insensitive, exact match).
         *
         * @param books array of books (may be null or contain nulls)
         * @param author the author name to search for
         * @return count of books by the specified author
         */
        public static int countByAuthor(Book[] books, String author) {
            if (books == null || author == null) {
                return 0;
            }

            int count = 0;
            for (Book book : books) {
                if (book != null && book.getAuthor().equalsIgnoreCase(author)) {
                    count++;
                }
            }
            return count;
        }

        /**
         * Filters books with price at most the specified maximum.
         * Returns a compact array (no nulls, exact size).
         *
         * @param books array of books (may be null or contain nulls)
         * @param maxPrice maximum price (inclusive)
         * @return compact array of books with price less than or equal to maxPrice
         * @throws IllegalArgumentException if maxPrice is negative
         */
        public static Book[] filterPriceAtMost(Book[] books, double maxPrice) {
            if (maxPrice < 0) {
                throw new IllegalArgumentException("Max price cannot be negative");
            }

            if (books == null) {
                return new Book[0];
            }

            // Count matching books
            int count = 0;
            for (Book book : books) {
                if (book != null && book.getPrice() <= maxPrice) {
                    count++;
                }
            }

            // Create compact array
            Book[] result = new Book[count];
            int index = 0;
            for (Book book : books) {
                if (book != null && book.getPrice() <= maxPrice) {
                    result[index++] = book;
                }
            }

            return result;
        }

        /**
         * Filters books published in a specific decade.
         * For example, decade 1990 includes years 1990-1999.
         *
         * @param books array of books (may be null or contain nulls)
         * @param decade the decade start year (e.g., 1990, 2000)
         * @return compact array of books from that decade
         */
        public static Book[] filterByDecade(Book[] books, int decade) {
            if (books == null) {
                return new Book[0];
            }

            int decadeEnd = decade + 9;

            // Count matching books
            int count = 0;
            for (Book book : books) {
                if (book != null && book.getYear() >= decade && book.getYear() <= decadeEnd) {
                    count++;
                }
            }

            // Create compact array
            Book[] result = new Book[count];
            int index = 0;
            for (Book book : books) {
                if (book != null && book.getYear() >= decade && book.getYear() <= decadeEnd) {
                    result[index++] = book;
                }
            }

            return result;
        }

        /**
         * Sorts books by price in ascending order (in-place).
         * Nulls are moved to the end.
         *
         * @param books array to sort (modified in-place)
         */
        public static void sortByPrice(Book[] books) {
            if (books == null || books.length <= 1) {
                return;
            }

            Arrays.sort(books, (a, b) -> {
                if (a == null && b == null) return 0;
                if (a == null) return 1;
                if (b == null) return -1;
                return Double.compare(a.getPrice(), b.getPrice());
            });
        }

        /**
         * Sorts books by year in ascending order (in-place).
         * Nulls are moved to the end.
         *
         * @param books array to sort (modified in-place)
         */
        public static void sortByYear(Book[] books) {
            if (books == null || books.length <= 1) {
                return;
            }

            Arrays.sort(books, (a, b) -> {
                if (a == null && b == null) return 0;
                if (a == null) return 1;
                if (b == null) return -1;
                return Integer.compare(a.getYear(), b.getYear());
            });
        }

        /**
         * Calculates the average price of books in the array.
         *
         * @param books array of books (may be null or contain nulls)
         * @return average price, or 0.0 if array is null or empty
         */
        public static double averagePrice(Book[] books) {
            if (books == null) {
                return 0.0;
            }

            double sum = 0.0;
            int count = 0;

            for (Book book : books) {
                if (book != null) {
                    sum += book.getPrice();
                    count++;
                }
            }

            return count == 0 ? 0.0 : sum / count;
        }

        /**
         * Finds the oldest book (earliest publication year).
         *
         * @param books array of books (may be null or contain nulls)
         * @return the oldest book, or null if array is null/empty
         */
        public static Book findOldest(Book[] books) {
            if (books == null) {
                return null;
            }

            Book oldest = null;
            for (Book book : books) {
                if (book != null) {
                    if (oldest == null || book.getYear() < oldest.getYear()) {
                        oldest = book;
                    }
                }
            }

            return oldest;
        }

        /**
         * Merges two book arrays into one, preserving all elements.
         *
         * @param arr1 first array (may be null)
         * @param arr2 second array (may be null)
         * @return merged array containing all books from both arrays
         */
        public static Book[] merge(Book[] arr1, Book[] arr2) {
            int len1 = (arr1 == null) ? 0 : arr1.length;
            int len2 = (arr2 == null) ? 0 : arr2.length;

            Book[] result = new Book[len1 + len2];

            if (arr1 != null) {
                System.arraycopy(arr1, 0, result, 0, len1);
            }
            if (arr2 != null) {
                System.arraycopy(arr2, 0, result, len1, len2);
            }

            return result;
        }

        /**
         * Removes duplicate books based on ISBN.
         * Returns a compact array with unique books only.
         *
         * @param books array of books (may be null or contain nulls)
         * @return compact array with duplicates removed
         */
        public static Book[] removeDuplicates(Book[] books) {
            if (books == null) {
                return new Book[0];
            }

            Set<String> seenIsbns = new HashSet<>();
            List<Book> unique = new ArrayList<>();

            for (Book book : books) {
                if (book != null && seenIsbns.add(book.getIsbn())) {
                    unique.add(book);
                }
            }

            return unique.toArray(new Book[0]);
        }

        /**
         * Finds books within a year range (inclusive).
         *
         * @param books array of books
         * @param startYear start year (inclusive)
         * @param endYear end year (inclusive)
         * @return compact array of books within the year range
         */
        public static Book[] filterByYearRange(Book[] books, int startYear, int endYear) {
            if (books == null || startYear > endYear) {
                return new Book[0];
            }

            return Stream.of(books)
                .filter(book -> book != null &&
                        book.getYear() >= startYear &&
                        book.getYear() <= endYear)
                .toArray(Book[]::new);
        }

        /**
         * Groups books by decade and returns a summary.
         *
         * @param books array of books
         * @return map with decade as key and count as value
         */
        public static Map<Integer, Integer> countByDecade(Book[] books) {
            Map<Integer, Integer> decadeCounts = new TreeMap<>();

            if (books != null) {
                for (Book book : books) {
                    if (book != null) {
                        int decade = (book.getYear() / 10) * 10;
                        decadeCounts.merge(decade, 1, Integer::sum);
                    }
                }
            }

            return decadeCounts;
        }

        /**
         * Finds the book with the longest title.
         *
         * @param books array of books
         * @return book with longest title, null if array is null/empty
         */
        public static Book findLongestTitle(Book[] books) {
            if (books == null) {
                return null;
            }

            Book longest = null;
            int maxLength = 0;

            for (Book book : books) {
                if (book != null && book.getTitle().length() > maxLength) {
                    maxLength = book.getTitle().length();
                    longest = book;
                }
            }

            return longest;
        }
    }