001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006     *
007     * Project Info:  http://www.jfree.org/jfreechart/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it 
010     * under the terms of the GNU Lesser General Public License as published by 
011     * the Free Software Foundation; either version 2.1 of the License, or 
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but 
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022     * USA.  
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     *
027     * ---------
028     * Year.java
029     * ---------
030     * (C) Copyright 2001-2007, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes
036     * -------
037     * 11-Oct-2001 : Version 1 (DG);
038     * 14-Nov-2001 : Override for toString() method (DG);
039     * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
040     * 29-Jan-2002 : Worked on parseYear() method (DG);
041     * 14-Feb-2002 : Fixed bug in Year(Date) constructor (DG);
042     * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 
043     *               evaluate with reference to a particular time zone (DG);
044     * 19-Mar-2002 : Changed API for TimePeriod classes (DG);
045     * 10-Sep-2002 : Added getSerialIndex() method (DG);
046     * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
047     * 10-Jan-2003 : Changed base class and method names (DG);
048     * 05-Mar-2003 : Fixed bug in getFirstMillisecond() picked up in JUnit 
049     *               tests (DG);
050     * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented 
051     *               Serializable (DG);
052     * 21-Oct-2003 : Added hashCode() method (DG);
053     * ------------- JFREECHART 1.0.x ---------------------------------------------
054     * 05-Oct-2006 : Updated API docs (DG);
055     * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG);
056     * 
057     */
058    
059    package org.jfree.data.time;
060    
061    import java.io.Serializable;
062    import java.util.Calendar;
063    import java.util.Date;
064    import java.util.TimeZone;
065    
066    import org.jfree.date.SerialDate;
067    
068    /**
069     * Represents a year in the range 1900 to 9999.  This class is immutable, which
070     * is a requirement for all {@link RegularTimePeriod} subclasses.
071     */
072    public class Year extends RegularTimePeriod implements Serializable {
073    
074        /** For serialization. */
075        private static final long serialVersionUID = -7659990929736074836L;
076        
077        /** The year. */
078        private short year;
079    
080        /** The first millisecond. */
081        private long firstMillisecond;
082        
083        /** The last millisecond. */
084        private long lastMillisecond;
085        
086        /**
087         * Creates a new <code>Year</code>, based on the current system date/time.
088         */
089        public Year() {
090            this(new Date());
091        }
092    
093        /**
094         * Creates a time period representing a single year.
095         *
096         * @param year  the year.
097         */
098        public Year(int year) {
099            if ((year < SerialDate.MINIMUM_YEAR_SUPPORTED)
100                || (year > SerialDate.MAXIMUM_YEAR_SUPPORTED)) {
101    
102                throw new IllegalArgumentException(
103                    "Year constructor: year (" + year + ") outside valid range.");
104            }
105            this.year = (short) year;
106            peg(Calendar.getInstance());
107        }
108    
109        /**
110         * Creates a new <code>Year</code>, based on a particular instant in time, 
111         * using the default time zone.
112         *
113         * @param time  the time (<code>null</code> not permitted).
114         */
115        public Year(Date time) {
116            this(time, RegularTimePeriod.DEFAULT_TIME_ZONE);
117        }
118    
119        /**
120         * Constructs a year, based on a particular instant in time and a time zone.
121         *
122         * @param time  the time.
123         * @param zone  the time zone.
124         */
125        public Year(Date time, TimeZone zone) {
126            Calendar calendar = Calendar.getInstance(zone);
127            calendar.setTime(time);
128            this.year = (short) calendar.get(Calendar.YEAR);
129            peg(calendar);
130        }
131    
132        /**
133         * Returns the year.
134         *
135         * @return The year.
136         */
137        public int getYear() {
138            return this.year;
139        }
140        
141        /**
142         * Returns the first millisecond of the year.  This will be determined 
143         * relative to the time zone specified in the constructor, or in the 
144         * calendar instance passed in the most recent call to the 
145         * {@link #peg(Calendar)} method.
146         *
147         * @return The first millisecond of the year.
148         * 
149         * @see #getLastMillisecond()
150         */
151        public long getFirstMillisecond() {
152            return this.firstMillisecond;
153        }
154    
155        /**
156         * Returns the last millisecond of the year.  This will be 
157         * determined relative to the time zone specified in the constructor, or
158         * in the calendar instance passed in the most recent call to the 
159         * {@link #peg(Calendar)} method.
160         *
161         * @return The last millisecond of the year.
162         * 
163         * @see #getFirstMillisecond()
164         */
165        public long getLastMillisecond() {
166            return this.lastMillisecond;
167        }
168        
169        /** 
170         * Recalculates the start date/time and end date/time for this time period 
171         * relative to the supplied calendar (which incorporates a time zone).
172         * 
173         * @param calendar  the calendar (<code>null</code> not permitted).
174         * 
175         * @since 1.0.3
176         */
177        public void peg(Calendar calendar) {
178            this.firstMillisecond = getFirstMillisecond(calendar);
179            this.lastMillisecond = getLastMillisecond(calendar);
180        }
181        
182        /**
183         * Returns the year preceding this one.
184         *
185         * @return The year preceding this one (or <code>null</code> if the 
186         *         current year is 1900).
187         */
188        public RegularTimePeriod previous() {
189            if (this.year > SerialDate.MINIMUM_YEAR_SUPPORTED) {
190                return new Year(this.year - 1);
191            }
192            else {
193                return null;
194            }
195        }
196    
197        /**
198         * Returns the year following this one.
199         *
200         * @return The year following this one (or <code>null</code> if the current
201         *         year is 9999).
202         */
203        public RegularTimePeriod next() {
204            if (this.year < SerialDate.MAXIMUM_YEAR_SUPPORTED) {
205                return new Year(this.year + 1);
206            }
207            else {
208                return null;
209            }
210        }
211    
212        /**
213         * Returns a serial index number for the year.
214         * <P>
215         * The implementation simply returns the year number (e.g. 2002).
216         *
217         * @return The serial index number.
218         */
219        public long getSerialIndex() {
220            return this.year;
221        }
222    
223        /**
224         * Returns the first millisecond of the year, evaluated using the supplied
225         * calendar (which determines the time zone).
226         *
227         * @param calendar  the calendar (<code>null</code> not permitted).
228         *
229         * @return The first millisecond of the year.
230         *
231         * @throws NullPointerException if <code>calendar</code> is 
232         *     <code>null</code>.
233         */
234        public long getFirstMillisecond(Calendar calendar) {
235            calendar.set(this.year, Calendar.JANUARY, 1, 0, 0, 0);
236            calendar.set(Calendar.MILLISECOND, 0);
237            // in the following line, we'd rather call calendar.getTimeInMillis()
238            // to avoid object creation, but that isn't supported in Java 1.3.1
239            return calendar.getTime().getTime();
240        }
241    
242        /**
243         * Returns the last millisecond of the year, evaluated using the supplied
244         * calendar (which determines the time zone).
245         *
246         * @param calendar  the calendar (<code>null</code> not permitted).
247         *
248         * @return The last millisecond of the year.
249         *
250         * @throws NullPointerException if <code>calendar</code> is 
251         *     <code>null</code>.
252         */
253        public long getLastMillisecond(Calendar calendar) {
254            calendar.set(this.year, Calendar.DECEMBER, 31, 23, 59, 59);
255            calendar.set(Calendar.MILLISECOND, 999);
256            // in the following line, we'd rather call calendar.getTimeInMillis()
257            // to avoid object creation, but that isn't supported in Java 1.3.1
258            return calendar.getTime().getTime();
259        }
260        
261        /**
262         * Tests the equality of this <code>Year</code> object to an arbitrary 
263         * object.  Returns <code>true</code> if the target is a <code>Year</code>
264         * instance representing the same year as this object.  In all other cases,
265         * returns <code>false</code>.
266         *
267         * @param object  the object (<code>null</code> permitted).
268         *
269         * @return <code>true</code> if the year of this and the object are the 
270         *         same.
271         */
272        public boolean equals(Object object) {
273            if (object != null) {
274                if (object instanceof Year) {
275                    Year target = (Year) object;
276                    return (this.year == target.getYear());
277                }
278                else {
279                    return false;
280                }
281            }
282            else {
283                return false;
284            }
285        }
286        
287        /**
288         * Returns a hash code for this object instance.  The approach described by
289         * Joshua Bloch in "Effective Java" has been used here:
290         * <p>
291         * <code>http://developer.java.sun.com/developer/Books/effectivejava
292         *     /Chapter3.pdf</code>
293         * 
294         * @return A hash code.
295         */
296        public int hashCode() {
297            int result = 17;
298            int c = this.year;
299            result = 37 * result + c;
300            return result;
301        }
302    
303        /**
304         * Returns an integer indicating the order of this <code>Year</code> object
305         * relative to the specified object:
306         *
307         * negative == before, zero == same, positive == after.
308         *
309         * @param o1  the object to compare.
310         *
311         * @return negative == before, zero == same, positive == after.
312         */
313        public int compareTo(Object o1) {
314    
315            int result;
316    
317            // CASE 1 : Comparing to another Year object
318            // -----------------------------------------
319            if (o1 instanceof Year) {
320                Year y = (Year) o1;
321                result = this.year - y.getYear();
322            }
323    
324            // CASE 2 : Comparing to another TimePeriod object
325            // -----------------------------------------------
326            else if (o1 instanceof RegularTimePeriod) {
327                // more difficult case - evaluate later...
328                result = 0;
329            }
330    
331            // CASE 3 : Comparing to a non-TimePeriod object
332            // ---------------------------------------------
333            else {
334                // consider time periods to be ordered after general objects
335                result = 1;
336            }
337    
338            return result;
339    
340        }
341    
342        /**
343         * Returns a string representing the year..
344         *
345         * @return A string representing the year.
346         */
347        public String toString() {
348            return Integer.toString(this.year);
349        }
350    
351        /**
352         * Parses the string argument as a year.
353         * <P>
354         * The string format is YYYY.
355         *
356         * @param s  a string representing the year.
357         *
358         * @return <code>null</code> if the string is not parseable, the year 
359         *         otherwise.
360         */
361        public static Year parseYear(String s) {
362    
363            // parse the string...
364            int y;
365            try {
366                y = Integer.parseInt(s.trim());
367            }
368            catch (NumberFormatException e) {
369                throw new TimePeriodFormatException("Cannot parse string.");
370            }
371    
372            // create the year...
373            try {
374                return new Year(y);
375            }
376            catch (IllegalArgumentException e) {
377                throw new TimePeriodFormatException("Year outside valid range.");
378            }
379        }
380    
381    }