001    package org.nakedobjects.applib.fixtures;
002    
003    import java.util.Calendar;
004    import java.util.TimeZone;
005    
006    import org.nakedobjects.applib.clock.Clock;
007    
008    
009    /**
010     * This clock, for use by fixtures, can be set to specific time.
011     * 
012     * <p>
013     * If not set it will provide the time provided by the system clock.
014     * 
015     * <p>
016     * Note that - by design - it does not provide any mechanism to advance the time (eg automatic ticking of the
017     * clock). That is, the time returned is always explicitly under the control of the programmer (it can be
018     * moved forward or back as required).
019     */
020    public class FixtureClock extends Clock {
021        private static final TimeZone UTC_TIME_ZONE;
022        
023        static {
024            TimeZone tempTimeZone = TimeZone.getTimeZone("Etc/UTC");
025            if (tempTimeZone == null) {
026                tempTimeZone = TimeZone.getTimeZone("UTC");
027            }
028            UTC_TIME_ZONE = tempTimeZone;
029        }
030    
031        
032        /**
033         * Configures the system to use a FixtureClock rather than the in-built system clock.  
034         * Can be called multiple times.
035         * 
036         * <p>
037         * Must call before any other call to {@link Clock#getInstance()}.
038         * 
039         * @throws IllegalStateException if Clock singleton already initialized with some other implementation.
040         */
041        public synchronized static FixtureClock initialize() {
042            if (!isInitialized() || 
043                    !(getInstance() instanceof FixtureClock)) { 
044                    // installs the FixtureClock as the Clock singleton via the Clock's constructor
045                    // if was initialized, then will replace.
046                    // (if non-replaceable, then superclass will throw exception for us.
047                    new FixtureClock(); 
048            }
049            return (FixtureClock) getInstance();
050        }
051    
052        /**
053         * Makes {@link Clock#remove()} visible.
054         */
055        public static boolean remove()  {
056            return Clock.remove();
057        }
058    
059    
060        
061        ////////////////////////////////////////////////////
062        // Constructor
063        ////////////////////////////////////////////////////
064    
065        // if non-null, then indicates that the time has been explicitly set.
066        // Otherwise returns the system time.
067        private Calendar calendar = null;
068    
069    
070        private FixtureClock() {}
071    
072    
073        ////////////////////////////////////////////////////
074        // hook
075        ////////////////////////////////////////////////////
076    
077        /**
078         * Access via {@link Clock#getTime()}.
079         * 
080         * <p>
081         * Will just return the system time until {@link #setDate(int, int, int)} or
082         * {@link #setTime(int, int)} (or one of the overloads) has been called.
083         */
084        @Override
085        protected long time() {
086            if (calendar == null) {
087                return System.currentTimeMillis();
088            }
089            return calendar.getTime().getTime();
090        }
091    
092        
093        ////////////////////////////////////////////////////
094        // setting/adjusting time
095        ////////////////////////////////////////////////////
096    
097        /**
098         * Sets the clock to epoch, that is midnight, 1 Jan 1970 UTC.
099         * 
100         * <p>
101         * This is typically called before either {@link #setDate(int, int, int)} 
102         * (so that time is set to midnight) and/or {@link #setTime(int, int)}
103         * (so that date is set to a well known value).
104         */
105        public void clear() {
106            setupCalendarIfRequired();
107            calendar.clear();
108        }
109    
110        /**
111         * Sets the hours and minutes as specified, and sets the seconds and
112         * milliseconds to zero, but the date portion is left unchanged.
113         * 
114         * @see #setDate(int, int, int)
115         * @see #addTime(int, int)
116         */
117        public void setTime(final int hour, final int min) {
118            setupCalendarIfRequired();
119            calendar.set(Calendar.HOUR_OF_DAY, hour);
120            calendar.set(Calendar.MINUTE, min);
121            calendar.set(Calendar.SECOND, 0);
122            calendar.set(Calendar.MILLISECOND, 0);
123        }
124    
125        /**
126         * Sets the date, but the time portion is left unchanged.
127         * 
128         * @see #setTime(int, int)
129         * @see #addDate(int, int, int)
130         */
131        public void setDate(final int year, final int month, final int day) {
132            setupCalendarIfRequired();
133            calendar.set(Calendar.YEAR, year);
134            calendar.set(Calendar.MONTH, month - 1);
135            calendar.set(Calendar.DAY_OF_MONTH, day);
136        }
137    
138        /**
139         * Adjusts the time by the specified number of hours and minutes.
140         * 
141         * <p>
142         * Typically called after {@link #setTime(int, int)}, to move the clock
143         * forward or perhaps back.
144         * 
145         * @see #addDate(int, int, int)
146         */
147        public void addTime(final int hours, final int minutes) {
148            setupCalendarIfRequired();
149            calendar.add(Calendar.HOUR_OF_DAY, hours);
150            calendar.add(Calendar.MINUTE, minutes);
151        }
152    
153        /**
154         * Adjusts the time by the specified number of years, months or days.
155         * 
156         * <p>
157         * Typically called after {@link #setDate(int, int, int)}, to move the clock
158         * forward or perhaps back.
159         * 
160         * @see #addTime(int, int)
161         */
162        public void addDate(final int years, final int months, final int days) {
163            setupCalendarIfRequired();
164            calendar.add(Calendar.YEAR, years);
165            calendar.add(Calendar.MONTH, months);
166            calendar.add(Calendar.DAY_OF_MONTH, days);
167        }
168    
169        private void setupCalendarIfRequired() {
170            if (calendar != null) {
171                    return;
172            }
173            calendar = Calendar.getInstance();
174            calendar.setTimeZone(UTC_TIME_ZONE);
175        }
176    
177        ////////////////////////////////////////////////////
178        // reset
179        ////////////////////////////////////////////////////
180    
181        /**
182         * Go back to just returning the system's time.
183         */
184        public void reset() {
185            calendar = null;
186        }
187    
188        ////////////////////////////////////////////////////
189        // toString
190        ////////////////////////////////////////////////////
191    
192        @Override
193        public String toString() {
194            return (calendar==null?"System":"Explicitly set") + 
195                            ": " + Clock.getTimeAsCalendar().getTime().toString();
196        }
197    }
198    // Copyright (c) Naked Objects Group Ltd.