public class Assert {
public static void assertTrue(
String message, boolean condition) { ...}
public static void assertEquals(
long expected, long actual) { ...}
...
}
This requires a
toString() method within your
Timeperiod class matching
Object.toString()'s
method signature.
1 day, 7 hours, 10 minutes and 23 seconds
Since the toString() method may
not be called at all it shall be implemented in a »on demand«
fashion similar to Your personal String class . Hint: create a suitable
attribute being null initially. If
toString() is being called, first check
for null and initialize it by the
desired output if so required.
Furthermore individual weeks, days, hours, minutes and
seconds shall be implemented as read-only values:
final Timeperiod tPeriod = new Timeperiod(2310983);
System.out.print("weeks = " + tPeriod.weeks);
tPeriod.days = 5; // Expected compile time error:
// Cannot assign a value to final variable 'days'
weeks = 3
In other words: Instances of
Timeperiod should be immutable
objects. Once a Timeperiod instance has
been created it shall be impossible to alter its internal
state.
In addition supply a so called copy constructor to allow
for creating a new instance from an existing one:
/**
* Clone a given instance.
*
* @param timeperiod Instance to be cloned.
*/
public Timeperiod(final Timeperiod timeperiod) { ... }
Use the following unit tests to check your
implementation's correctness:
public class TimeperiodTest {
// Helper methods for real tests.
static void assertPeriodEqualImplement(
final int expectedSeconds,
final int expectedMinutes,
final int expectedHours,
final int expectedDays,
final int expectedWeeks,
final String expectedToString,
final Timeperiod period) {
Assert.assertEquals(expectedSeconds, period.seconds);
Assert.assertEquals(expectedMinutes, period.minutes);
Assert.assertEquals(expectedHours, period.hours);
Assert.assertEquals(expectedDays, period.days);
Assert.assertEquals(expectedWeeks, period.weeks);
Assert.assertEquals(expectedToString, period.toString());
}
static void assertPeriodEqual(
final int expectedSeconds,
final int expectedMinutes,
final int expectedHours,
final int expectedDays,
final int expectedWeeks,
final String expectedToString,
final Timeperiod period) {
// Testing period in question
assertPeriodEqualImplement(
expectedSeconds, expectedMinutes, expectedHours, expectedDays, expectedWeeks, expectedToString,
period);
// Testing copy constructor
final Timeperiod periodClone = new Timeperiod(period);
Assert.assertTrue("A cloned instance must differ from its original", periodClone != period);
assertPeriodEqualImplement(
expectedSeconds, expectedMinutes, expectedHours, expectedDays, expectedWeeks, expectedToString,
periodClone);
}
/**
* Test constructor zero seconds.
*/
@Test
public void testZero() {
assertPeriodEqual(
0,0,0,0,0,
"0 seconds",
new Timeperiod(0));
}
@Test
public void testMinute() {
assertPeriodEqual(0,1,0,0,0,
"1 minute and 0 seconds",
new Timeperiod(60));
assertPeriodEqual(50,0,0,0,0,
"50 seconds",
new Timeperiod(50));
assertPeriodEqual(12,1,0,0,0,
"1 minute and 12 seconds",
new Timeperiod(72));
assertPeriodEqual(2,5,0,0,0,
"5 minutes and 2 seconds",
new Timeperiod(302));
}
@Test
public void testHour() {
assertPeriodEqual(0,0,2,0,0,
"2 hours, 0 minutes and 0 seconds",
new Timeperiod(7200));
assertPeriodEqual(59,59,0,0,0,
"59 minutes and 59 seconds",
new Timeperiod(3599));
assertPeriodEqual(40,1,1,0,0,
"1 hour, 1 minute and 40 seconds",
new Timeperiod(3700));
}
@Test
public void testVarious() {
assertPeriodEqual(1,3,4,1,6,
"6 weeks, 1 day, 4 hours, 3 minutes and 1 second",
new Timeperiod(3729781));
assertPeriodEqual(23,56,17,5,3,
"3 weeks, 5 days, 17 hours, 56 minutes and 23 seconds",
new Timeperiod(2310983));
}
}
A:
Our implementation basically reads:
public class Timeperiod {
// Constructors, toString() and other methods
// ...
public final int seconds, minutes, hours, days, weeks; // Instance state
}
The final modifier ensures
instances being immutable thus requiring all values to be set
within any constructor being defined. We thus decompose the
desired number of seconds into weeks, days, hours, minutes and
remaining seconds:
public Timeperiod(int seconds) {
weeks = seconds / SECONDS_PER_WEEK;
seconds = seconds % SECONDS_PER_WEEK; // remaining seconds without weeks
days = seconds / SECONDS_PER_DAY;
seconds %= SECONDS_PER_DAY; // remaining seconds without days
hours = seconds / SECONDS_PER_HOUR;
seconds %= SECONDS_PER_HOUR; // remaining seconds without minutes
minutes = seconds / SECONDS_PER_MINUTE;
this.seconds = seconds % SECONDS_PER_MINUTE; // remaining seconds
}
Notice the this.seconds qualification being
required to disambiguate the constructor parameter variable
Timeperiod(int seconds) scope from the instance
member variable scope Timeperiod.seconds.
The toString() method could be defined
straightforwardly:
public String toString() {
final int largestNonZeroComponent;
if (0 < weeks) {
largestNonZeroComponent = 5; // weeks, days, hours, minutes and seconds
} else if (0 < days) {
largestNonZeroComponent = 4; // days, hours, minutes and seconds
} else if (0 < hours) {
largestNonZeroComponent = 3; // hours, minutes and seconds
} else if (0 < minutes) {
largestNonZeroComponent = 2; // minutes and seconds
} else {
largestNonZeroComponent = 1; // only seconds, potentially zero as well
}
final StringBuffer buffer = new StringBuffer();
switch (largestNonZeroComponent) {
case 5: addSingularOrPlural(buffer, weeks, "week", ", ");
case 4: addSingularOrPlural(buffer, days, "day", ", ");
case 3: addSingularOrPlural(buffer, hours, "hour", ", ");
case 2: addSingularOrPlural(buffer, minutes, "minute", ", ");
case 1: addSingularOrPlural(buffer, seconds, "second", " and ");
}
return buffer.toString();
}
This solution provides the desired result. However if
being called repeatedly it causes a performance penalty
recalculating an identical value time and again. We thus refine
it by using a lazy initialization mechanism. In the first step
we rename our current method implementation having just private access:
private String toStringImplement() {
final int largestNonZeroComponent;
if (0 < weeks) {
largestNonZeroComponent = 5;
...
return buffer.toString();
}
We now re-implement our desired
toString() method in a lazy
initialization fashion by introducing an additional
private attribute:
public class Timeperiod {
// Tedious calculation, will be be initialized on-demand only
private String toStringValue = null;
...
public String toString() {
// Calculation is tedious, thus performing it only on-demand.
//
if (null == toStringValue) { // Called for the first time, not yet initialized?
toStringValue = toStringImplement();
}
return toStringValue;
}
}
See Timeperiod
for a complete solution including a copy constructor
implementation.