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();
}
}
1 Answer 1
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 thanThrowable
- Nested
Map
s are perfect candidates for new classes. - I don't understand why you would have a
Map
linkingLocalDate
s toSeance
s and then toLocalTime
. 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 atoJson
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 aMap
,arr
forString[]
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; }
justreturn 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!