3
\$\begingroup\$

I wrote my first application about custom exceptions and catching them. Here is the GitHub link.

I Have 3 types of custom exceptions:

  • TimeException (validate time: hours, minutes)

  • MovieException (validate movie name, director, genres) - from properies file

    • SeanceException (validate price, room, movie length) - from properies file

All this exception must be caught and save to map> (Name exception, list exceptions)


Class Repertoire gets values from file .txt and check from date range examples line in file:

Date;MovieName;Gener;Director;TicketPrice;MovieLength;Room;DateofSeance

2017-07-12;NOVA;Comedy;Eddie Murder;24;180;9;26:00,12:00,17:00
public class Repertoire {
    private Map<LocalDate, Map<Seance, List<LocalTime>>> map = new HashMap<LocalDate, Map<Seance, List<LocalTime>>>();

    @Override
    public String toString() {
        return "cinema.Repertoire{" +
                "map=" + map +
                '}';
    }

    public Map<LocalDate, Map<Seance, List<LocalTime>>> getMap() {
        return map;
    }

    public void setMap(Map<LocalDate, Map<Seance, List<LocalTime>>> map) {
        this.map = map;
    }

    public Repertoire(String fileName, LocalDate minDate, LocalDate maxDate) {

        Map<LocalDate, Map<Seance, List<LocalTime>>> map = new LinkedHashMap<>();
        try {
            FileReader fr = new FileReader(fileName);
            Scanner scanner = new Scanner(fr);
            while (scanner.hasNextLine()) {
                Map<Seance, List<LocalTime>> seanceListMap = new HashMap<>();
                String[] line = scanner.nextLine().split(";");
                LocalDate localDate = LocalDate.parse(line[0]);
                if (localDate.isAfter(minDate) && localDate.isBefore(maxDate))
                {
                    try {
                        Movie film = null;
                        film = new Movie(line[1], line[2], line[3]);
                        Seance seance = new Seance(film, Integer.parseInt(line[4]), Integer.parseInt(line[5]), Integer.parseInt(line[6]));
                        String[] time = line[7].split(",");
                        List<LocalTime> timeList = new ArrayList<>();
                        for (String str : time)
                        {
                                String[] tmp = str.split(":");
                                if(!checkHours(Integer.parseInt(tmp[0])))
                                {
                                    throw new ValidationTimeException(film.getTitleMovie(), Double.parseDouble(tmp[0]), LocalDateTime.now());
                                }
                                if(!checkMinuts(Integer.parseInt(tmp[1])))
                                {
                                 throw new ValidationTimeException(film.getTitleMovie(), Double.parseDouble(tmp[1]), LocalDateTime.now());
                                }
                                timeList.add(LocalTime.parse(str));
                        }
                        if (map.containsKey(localDate))
                        {
                            map.get(localDate).put(seance, timeList);
                        } else {
                            seanceListMap.put(seance, timeList);
                            map.put(localDate, seanceListMap);
                        }
                    } catch (exception.CustomException e) {
                        exception.CustomException.addNewException(e);
                    }
                }
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        this.map =  map;
    }


    private boolean checkHours(int hour)
    {
        return  hour < 1 ? false : hour > 24 ?  false : true;
    }

    private boolean checkMinuts(int minut)
    {
        return  minut < 0 ? false : minut > 59 ?  false : true;
    }

Class Movie validates genres from properties file, movie name(only big letter one word) and director (1st big letter two words)

genres = Comedy,Adventure,Romance,Thriller,Horror
public class Movie {

    private String titleMovie;
    private String genreMovie;
    private String director;


    public Movie(String titleMovie, String genreMovie, String director) throws ValidationMovieException {
            this.titleMovie = titleMovie;
        if(!validateTitle())
        {
            throw new ValidationMovieException(titleMovie, LocalDateTime.now());
        }
            this.genreMovie = genreMovie;
        if(!validateGenre())
        {
            throw new ValidationMovieException(genreMovie, LocalDateTime.now());
        }
            this.director = director;
        if(!validateDirector())
        {
            throw new ValidationMovieException(director, LocalDateTime.now());
        }

    }

    public Movie() {
    }

    @Override
    public String toString() {
        return "cinema.Movie{" +
                "titleMovie='" + titleMovie + '\'' +
                ", genreMovie='" + genreMovie + '\'' +
                ", director='" + director + '\'' +
                '}';
    }

    public String getTitleMovie() {
        return titleMovie;
    }

    public void setTitleMovie(String titleMovie) {
        this.titleMovie = titleMovie;
    }

    public String getGenreMovie() {
        return genreMovie;
    }

    public void setGenreMovie(String genreMovie) {
        this.genreMovie = genreMovie;
    }

    public String getDirector() {
        return director;
    }

    public void setDirector(String director) {
        this.director = director;
    }

    private boolean checkField(String str, String regex) {
        return str.matches(regex);
    }

    private boolean validateTitle() {

        return checkField(titleMovie, "[A-Z]+");
    }

    private boolean validateGenre() {
        String[] arr = movieProperties();
        for (int i = 0; i < arr.length ; i++)
        {
            if(genreMovie.equals(arr[i]))
                return true;
        }
        return false;
    }

    private boolean validateDirector() {
        return checkField(director, "[A-Z][a-z]+ [A-Z][a-z]+");
    }

    private String[] movieProperties() {
        Properties properties = new Properties();
        String[] arr = new String[]{};
        try {
            InputStream inputStream = new FileInputStream("src\\main\\resources\\geners.properties");
            properties.load(inputStream);
            arr = properties.getProperty("genres").split(",");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return arr;
    }

Seance class validates Price min, max move length(1,2,3), room min max

public class Seance {

    private Movie movie;
    private int price;
    private int movieLength;
    private int roomNumber;

    @Override
    public String toString() {
        return "cinema.Seance{" +
                "move=" + movie +
                ", price=" + price +
                ", movieLength=" + movieLength +
                ", roomNumber=" + roomNumber +
                '}';
    }

    public Movie getMovie() {
        return movie;
    }

    public void setMovie(Movie movie) {
        this.movie = movie;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public int getMovieLength() {
        return movieLength;
    }

    public void setMovieLength(int movieLength) {
        this.movieLength = movieLength;
    }

    public int getRoomNumber() {
        return roomNumber;
    }

    public void setRoomNumber(int roomNumber) {
        this.roomNumber = roomNumber;
    }

    public Seance(Movie movie, int price, int movieLength, int roomNumber) throws ValidationSeanceException {
        Map<String, Integer> map = properties();
        this.movie = movie;
        this.price = price;
        if(!checkPrice(price,map.get("PRICE_MIN"), map.get("PRICE_MAX")))
        {
            throw new ValidationSeanceException(movie.getTitleMovie(), "Price " + price , LocalDateTime.now());
        }
        this.movieLength = movieLength;
        if(!checkMovieLength(movieLength,map.get("MOVIE_TIME1"), map.get("MOVIE_TIME2"), map.get("MOVIE_TIME3")))
        {
            throw new ValidationSeanceException(movie.getTitleMovie(), "movie length " + movieLength , LocalDateTime.now());
        }
        this.roomNumber = roomNumber;
        if(!checkCinemaHall(roomNumber,map.get("ROOM_MIN"), map.get("ROOM_MAX")))
        {
            throw new ValidationSeanceException(movie.getTitleMovie(), "room " +  roomNumber , LocalDateTime.now());
        }
    }



    private Map<String, Integer> properties()
    {

        Map<String, Integer> map = new HashMap<>();
        try {
            Properties properties = new Properties();
            InputStream inputStream = new FileInputStream("src\\main\\resources\\seance.properties");
            properties.load(inputStream);
            map.put("PRICE_MIN", Integer.parseInt(properties.getProperty("PRICE_MIN")));
            map.put("PRICE_MAX", Integer.parseInt(properties.getProperty("PRICE_MAX")));
            map.put("MOVIE_TIME1", Integer.parseInt(properties.getProperty("MOVIE_TIME1")));
            map.put("MOVIE_TIME2", Integer.parseInt(properties.getProperty("MOVIE_TIME2")));
            map.put("MOVIE_TIME3", Integer.parseInt(properties.getProperty("MOVIE_TIME3")));
            map.put("ROOM_MAX", Integer.parseInt(properties.getProperty("ROOM_MAX")));
            map.put("ROOM_MIN", Integer.parseInt(properties.getProperty("ROOM_MIN")));
        } catch (IOException e){
            e.printStackTrace();
        }
        return  map;
    }

    private boolean checkPrice(int price, int minPrice, int maxPrice)
    {
        if(price > minPrice && price < maxPrice)
            return true;
        else
            return false;

    }

    private boolean checkMovieLength(int movieLength, int movieLength_1, int movieLength_2, int movieLength_3)
    {
        if(movieLength == movieLength_1 || movieLength == movieLength_2 || movieLength == movieLength_3)
            return true;
        else
            return false;

    }

    private boolean checkCinemaHall(int hall, int minHall, int maxHall)
    {
        if(hall > minHall && hall < maxHall)
            return true;
        else
            return false;
    }

Custom Exception class catches all exceptions and writes them to map. You can write the Earliest, occuring exception, clear map and save to file.

public class CustomException extends  Throwable {

    private static Map<String, List<String>> map = new HashMap<>();

    public CustomException() {
    }

    public static void addNewException(CustomException c)
    {
        String className = "";
        if (c instanceof ValidationMovieException)
        {
            className = ValidationMovieException.class.getCanonicalName();
        }
        else if (c instanceof ValidationSeanceException)
        {
            className = ValidationSeanceException.class.getCanonicalName();
        }
        else if (c instanceof ValidationTimeException)
        {
            className = ValidationTimeException.class.getCanonicalName();
        }

        if (getMap().containsKey(className))
        {
            getMap().get(className).add(c.getMessage());
        }
        else
        {
            getMap().put(className, new ArrayList<>(Arrays.asList(c.getMessage())));

        }
    }

    private static LocalDateTime getExceptionTime(String s)
    {
        String[] elements = s.split(", ");
        return LocalDateTime.parse(elements[elements.length - 1], DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
    }
    public static String theEarliestException()
    {
        String exception =  map
                .entrySet()
                .stream()
                .flatMap(e -> e.getValue().stream())
                .sorted(Comparator.comparing(CustomException::getExceptionTime))
                .findFirst()
                .orElseThrow(() -> new NullPointerException("Can't view first exception"));


        switch (exception.split(", ")[0])
        {
            case "VALIDATION MOVIE":
                return ValidationMovieException.class.getCanonicalName();
            case "VALIDATION SEANCE":
                return ValidationSeanceException.class.getCanonicalName();
            case "VALIDATION TIME":
                return ValidationTimeException.class.getCanonicalName();
            default:
                return "";
        }

    }

    public static String theMostOccuringException()
    {
        return map
                .entrySet()
                .stream()
                .sorted((e1,e2) -> Integer.compare(e2.getValue().size(), e1.getValue().size()))
                .findFirst()
                .orElseThrow(() -> new NullPointerException("Can't get exception"))
                .getKey();
    }


    public static void clearMap()
    {
        map.clear();
    }

    public static Map<String, List<String>> getMap() {
        return map;
    }


    public static void saveToFile(String fileName)
    {
        try {
            FileWriter fw = new FileWriter(fileName, true);
            PrintWriter pw = new PrintWriter(fw);
            Scanner scanner = new Scanner(System.in);
            map.forEach((k,v) -> pw.println(k + ":\n" + v));
            pw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

This is going to be rather generic, because a full review of this much code will take a long time. That said:

  • Extend Exception rather than Throwable
  • Nested Maps are perfect candidates for new classes.
  • I don't understand why you would have a Map linking LocalDates to Seances and then to LocalTime. Are you sure the date and time shouldn't be properties of the Seance?
  • Overriding toString everywhere will surprise maintainers, because it's not at all common in my experience. If you need to output instances in some serialization format like JSON then write a toJson or equivalent method, or better yet use a framework which lets you decorate classes as serializable.
  • Using lots of getters and setters is an antipattern. I recommend reading about when to use them and adapting the code accordingly.
  • Lots of static methods is another antipattern.
  • Names like map for a Map, arr for String[] and single characters carry no meaning, and as such makes the program hard to understand. Naming is famously difficult, but also rewarding if resolved properly, not least because it often gives you a hint that something should be refactored.
  • Rather than if (foo) { return true; } else { return false; } just return foo.
  • Why pass along the current time to your exception? Your logging framework can easily timestamp entries for you.

PS: It would be great to see another question once you have tweaked the code to your own satisfaction!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.