Is a Year a Leap Year?
This program determines if the first argument passed to it is a leap year, and prints the result.
A year is a leap year if it is divisible by 4, unless it is divisible by 100. However, years divisible by 400 are leap years.
The program internally uses a method that throws one of two exceptions: one if the year is a leap year, and one if it isn't. Because these exception classes don't do anything different from the built-in Exception class, they don't need to override any methods; they can simply be declared and used.
To convert the command-line parameter from a string to a number, the program uses the static method parseLong() from the class Long, which is a class that wraps the primitive type long. Because parseLong() is a static method, it is not called on an instance of the class.
parseLong() is defined to throw NumberFormatException if the input string cannot be converted to a number. Because checkLeapYear() throws the two user-defined exceptions and, thus, typically is called inside a try block, it is not too much work to also catch NumberFormatException.
NumberFormatException is a runtime exception and, therefore, does not have to be listed in the throws clause of the declaration of checkLeapYear(), but it is included because throwing that exception is the designated way to handle an invalid input.
Source Code
1. public class IsLeapYear { 2. 3. public static class LeapYearException 4. extends Exception {} 5. public static class NotLeapYearException 6. extends Exception {} 7. 8. static void checkLeapYear(String year) 9. throws LeapYearException, NotLeapYearException, 10. NumberFormatException { 11. 12. long yearAsLong = Long.parseLong(year); 13. 14. // 15. // A leap year is a multiple of 4, unless it is 16. // a multiple of 100, unless it is a multiple of 17. // 400. 18. // 19. // We calculate the three values, then make a 20. // 3-bit binary value out of them and look it up 21. // in results. 22. // 23. 24. final boolean results[] = 25. { true, false, false, true, 26. false, false, false, false }; 27. 28. if (results[ 29. ((((yearAsLong % 4) == 0) ? 1 : 0) << 2) + 30. ((((yearAsLong % 100) == 0) ? 1 : 0) << 1) + 31. ((((yearAsLong % 400) == 0) ? 1 : 0) << 0)]) { 32. throw new LeapYearException(); 33. } else { 34. throw new NotLeapYearException(); 35. } 36. } 37. 38. public static void main(String[] args) { 39. 40. if (args.length > 0) { 41. 42. try { 43. checkLeapYear(args[0]); 44. } catch ( NumberFormatException nfe ) { 45. System.out.println( 46. "Invalid argument: " + 47. nfe.getMessage()); 48. } catch ( LeapYearException lye ) { 49. System.out.println( 50. args[0] + " is a leap year"); 51. } catch ( NotLeapYearException nlye ) { 52. System.out.println( 53. args[0] + " is not a leap year"); 54. } 55. } 56. } 57. }
Suggestions
What exactly does it mean if a bit is on in results?
Because the value computed on lines 2931 is immediately used to index into results, an array of size 8, is it guaranteed that this value will be properly restricted so as to not produce an invalid array index?
How many possible kinds of years are there, and given that it is less than the size of results, are there certain values for the index into results that will never occur?
Hints
Walk through main() with the following values for args[0]:
Multiple of 100 is not a leap year: "1900"
Multiple of 4 is a leap year: "1904"
Multiple of 400 is a leap year: "2000"
Any other year is not a leap year: "2001"
Explanation of the Bug
Not surprisingly, the problem is in the declaration of the results array on lines 2426. It is reversed; that is, it is declared as if the calculation of the index on lines 2931 had every bit flipped, which is an F.init error. The proper declaration should be as follows:
private static final boolean results[] = { false, false, false, false, true, false, false, true };
Alternately, the assignment of the bits on lines 2931 could be flipped.
With the bits assigned as-is, if a year is divisible by 400, the low bit in the index will be on, which means that the index in binary is in the form xx1. Because such years are also divisible by 4 and 100, the next two bits are also on. Thus, indices 1, 3, and 5 in results won't ever be used. Years divisible by 400 always wind up with an index of 7 (binary 111).
If year is not divisible by 400, but is divisible by 100, the low bit is off, but the second bit is on, so the index is in the form x10. Because such a year is also divisible by 4, the index is always 6 (binary 110), and index 2 is never used.
For the rest of the years, those not divisible by 100 (or 400), the index is in the form x00. Years divisible by 4 result in an index of 4 (binary 100), and those not divisible by 4 result in an index of 0.
Therefore, the only indices that matter are 0, 4, 6, and 7. Of those, 0 and 6 should be false (not leap years), and 4 and 7 should be true (leap years). Because of the bug, those indices had their bits flipped, so 7 and 1 were false, and 3 and 0 were true. The "unused" indices (2, 4, 5, and 6) were also set to false.
Thus, the program as written would, by chance, work if a year was divisible by 100 but not by 400 (a year such as 1900), correctly reporting, based on the value of index 6, that such a year was not a leap year. It would misrepresent the years 2000 (index 7) and 2004 (index 4) as not being leap years, and the year 2001 (index 0) as being a leap year.