001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ----------------------
028 * RegularTimePeriod.java
029 * ----------------------
030 * (C) Copyright 2001-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 * 
035 */
036
037package org.jfree.data.time;
038
039import java.lang.reflect.Constructor;
040import java.util.Calendar;
041import java.util.Date;
042import java.util.Locale;
043import java.util.TimeZone;
044import java.util.concurrent.atomic.AtomicReference;
045
046import org.jfree.chart.date.MonthConstants;
047
048/**
049 * An abstract class representing a unit of time.  Convenient methods are
050 * provided for calculating the next and previous time periods.  Conversion
051 * methods are defined that return the first and last milliseconds of the time
052 * period.  The results from these methods are timezone dependent.
053 * <P>
054 * This class is immutable, and all subclasses should be immutable also.
055 */
056public abstract class RegularTimePeriod implements TimePeriod, Comparable,
057        MonthConstants {
058
059    private static final AtomicReference<Calendar> calendarPrototype = new AtomicReference<>();
060
061    private static final ThreadLocal<Calendar> threadLocalCalendar = new ThreadLocal<>();
062
063    /**
064     * Creates a time period that includes the specified millisecond, assuming
065     * the given time zone.
066     *
067     * @param c  the time period class.
068     * @param millisecond  the time.
069     * @param zone  the time zone.
070     * @param locale  the locale.
071     *
072     * @return The time period.
073     */
074    public static RegularTimePeriod createInstance(Class c, Date millisecond,
075            TimeZone zone, Locale locale) {
076        RegularTimePeriod result = null;
077        try {
078            Constructor constructor = c.getDeclaredConstructor(
079                    new Class[] {Date.class, TimeZone.class, Locale.class});
080            result = (RegularTimePeriod) constructor.newInstance(
081                    new Object[] {millisecond, zone, locale});
082        }
083        catch (Exception e) {
084            // do nothing, so null is returned
085        }
086        return result;
087    }
088
089    /**
090     * Returns a subclass of {@link RegularTimePeriod} that is smaller than
091     * the specified class.
092     *
093     * @param c  a subclass of {@link RegularTimePeriod}.
094     *
095     * @return A class.
096     */
097    public static Class downsize(Class c) {
098        if (c.equals(Year.class)) {
099            return Quarter.class;
100        }
101        else if (c.equals(Quarter.class)) {
102            return Month.class;
103        }
104        else if (c.equals(Month.class)) {
105            return Day.class;
106        }
107        else if (c.equals(Day.class)) {
108            return Hour.class;
109        }
110        else if (c.equals(Hour.class)) {
111            return Minute.class;
112        }
113        else if (c.equals(Minute.class)) {
114            return Second.class;
115        }
116        else if (c.equals(Second.class)) {
117            return Millisecond.class;
118        }
119        else {
120            return Millisecond.class;
121        }
122    }
123
124    /**
125     * Creates or returns a thread-local Calendar instance.
126     * This function is used by the various subclasses to obtain a calendar for
127     * date-time to/from ms-since-epoch conversions (and to determine
128     * the first day of the week, in case of {@link Week}).
129     * <p>
130     * If a thread-local calendar was set with {@link #setThreadLocalCalendarInstance(Calendar)},
131     * then it is simply returned.
132     * <p>
133     * Otherwise, If a global calendar prototype was set with {@link #setCalendarInstancePrototype(Calendar)},
134     * then it is cloned and set as the thread-local calendar instance for future use,
135     * as if it was set with {@link #setThreadLocalCalendarInstance(Calendar)}.
136     * <p>
137     * Otherwise, if neither is set, a new instance will be created every
138     * time with {@link Calendar#getInstance()}, resorting to JFreeChart 1.5.0
139     * behavior (leading to huge load on GC and high memory consumption
140     * if many instances are created).
141     *
142     * @return a thread-local Calendar instance
143     */
144    protected static Calendar getCalendarInstance() {
145        Calendar calendar = threadLocalCalendar.get();
146        if (calendar == null) {
147            Calendar prototype = calendarPrototype.get();
148            if (prototype != null) {
149                calendar = (Calendar) prototype.clone();
150                threadLocalCalendar.set(calendar);
151            }
152        }
153        return calendar != null ? calendar : Calendar.getInstance();
154    }
155
156    /**
157     * Sets the thread-local calendar instance for time calculations.
158     * <p>
159     * {@code RegularTimePeriod} instances sometimes need a {@link Calendar}
160     * to perform time calculations (date-time from/to milliseconds-since-epoch).
161     * In JFreeChart 1.5.0, they created a new {@code Calendar} instance
162     * every time they needed one.  This created a huge load on GC and lead
163     * to high memory consumption.  To avoid this, a thread-local {@code Calendar}
164     * instance can be set, which will then be used for time calculations
165     * every time, unless the caller passes a specific {@code Calendar}
166     * instance in places where the API allows it.
167     * <p>
168     * If the specified calendar is {@code null}, or if this method was never called,
169     * then the next time a calendar instance is needed, a new one will be created by cloning
170     * the global prototype set with {@link #setCalendarInstancePrototype(Calendar)}.
171     * If none was set either, then a new instance will be created every time
172     * with {@link Calendar#getInstance()}, resorting to JFreeChart 1.5.0 behavior.
173     *
174     * @param calendar the new thread-local calendar instance
175     */
176    public static void setThreadLocalCalendarInstance(Calendar calendar) {
177        threadLocalCalendar.set(calendar);
178    }
179
180
181    /**
182     * Sets a global calendar prototype for time calculations.
183     * <p>
184     * {@code RegularTimePeriod} instances sometimes need a {@link Calendar}
185     * to perform time calculations (date-time from/to milliseconds-since-epoch).
186     * In JFreeChart 1.5.0, they created a new {@code Calendar} instance
187     * every time they needed one.  This created a huge load on GC and lead
188     * to high memory consumption.  To avoid this, a prototype {@code Calendar}
189     * can be set, which will be then cloned by every thread that needs
190     * a {@code Calendar} instance.  The prototype is not cloned right away,
191     * and stored instead for later cloning, therefore the caller must not
192     * alter the prototype after it has been passed to this method.
193     * <p>
194     * If the prototype is {@code null}, then thread-local calendars
195     * set with {@link #setThreadLocalCalendarInstance(Calendar)} will be
196     * used instead.  If none was set for some thread, then a new instance will be
197     * created with {@link Calendar#getInstance()} every time one is needed.
198     * However, if the prototype was already cloned by some thread,
199     * then setting it to {@code null} has no effect, and that thread must
200     * explicitly set its own instance to {@code null} or something else to get
201     * rid of the cloned calendar.
202     * <p>
203     * Calling {@code setCalendarInstancePrototype(Calendar.getInstance())}
204     * somewhere early in an application will effectively mimic JFreeChart
205     * 1.5.0 behavior (using the default calendar everywhere unless explicitly
206     * specified), while preventing the many-allocations problem.  There is one
207     * important caveat, however: once a prototype is cloned by some
208     * thread, calling {@link TimeZone#setDefault(TimeZone)}
209     * or {@link Locale#setDefault(Locale)}} will have no
210     * effect on future calculations.  To avoid this problem, simply set
211     * the default time zone and locale before setting the prototype.
212     *
213     * @param calendar the new thread-local calendar instance
214     */
215    public static void setCalendarInstancePrototype(Calendar calendar) {
216        calendarPrototype.set(calendar);
217    }
218
219    /**
220     * Returns the time period preceding this one, or {@code null} if some
221     * lower limit has been reached.
222     *
223     * @return The previous time period (possibly {@code null}).
224     */
225    public abstract RegularTimePeriod previous();
226
227    /**
228     * Returns the time period following this one, or {@code null} if some
229     * limit has been reached.
230     *
231     * @return The next time period (possibly {@code null}).
232     */
233    public abstract RegularTimePeriod next();
234
235    /**
236     * Returns a serial index number for the time unit.
237     *
238     * @return The serial index number.
239     */
240    public abstract long getSerialIndex();
241
242    //////////////////////////////////////////////////////////////////////////
243
244    /**
245     * Recalculates the start date/time and end date/time for this time period
246     * relative to the supplied calendar (which incorporates a time zone).
247     *
248     * @param calendar  the calendar ({@code null} not permitted).
249     */
250    public abstract void peg(Calendar calendar);
251
252    /**
253     * Returns the date/time that marks the start of the time period.  This
254     * method returns a new {@code Date} instance every time it is called.
255     *
256     * @return The start date/time.
257     *
258     * @see #getFirstMillisecond()
259     */
260    @Override
261    public Date getStart() {
262        return new Date(getFirstMillisecond());
263    }
264
265    /**
266     * Returns the date/time that marks the end of the time period.  This
267     * method returns a new {@code Date} instance every time it is called.
268     *
269     * @return The end date/time.
270     *
271     * @see #getLastMillisecond()
272     */
273    @Override
274    public Date getEnd() {
275        return new Date(getLastMillisecond());
276    }
277
278    /**
279     * Returns the first millisecond of the time period.  This will be
280     * determined relative to the time zone specified in the constructor, or
281     * in the calendar instance passed in the most recent call to the
282     * {@link #peg(Calendar)} method.
283     *
284     * @return The first millisecond of the time period.
285     *
286     * @see #getLastMillisecond()
287     */
288    public abstract long getFirstMillisecond();
289
290    /**
291     * Returns the first millisecond of the time period, evaluated using the
292     * supplied calendar (which incorporates a timezone).
293     *
294     * @param calendar  the calendar ({@code null} not permitted).
295     *
296     * @return The first millisecond of the time period.
297     *
298     * @throws NullPointerException if {@code calendar} is {@code null}.
299     *
300     * @see #getLastMillisecond(Calendar)
301     */
302    public abstract long getFirstMillisecond(Calendar calendar);
303
304    /**
305     * Returns the last millisecond of the time period.  This will be
306     * determined relative to the time zone specified in the constructor, or
307     * in the calendar instance passed in the most recent call to the
308     * {@link #peg(Calendar)} method.
309     *
310     * @return The last millisecond of the time period.
311     *
312     * @see #getFirstMillisecond()
313     */
314    public abstract long getLastMillisecond();
315
316    /**
317     * Returns the last millisecond of the time period, evaluated using the
318     * supplied calendar (which incorporates a timezone).
319     *
320     * @param calendar  the calendar ({@code null} not permitted).
321     *
322     * @return The last millisecond of the time period.
323     *
324     * @see #getFirstMillisecond(Calendar)
325     */
326    public abstract long getLastMillisecond(Calendar calendar);
327
328    /**
329     * Returns the millisecond closest to the middle of the time period.
330     *
331     * @return The middle millisecond.
332     */
333    public long getMiddleMillisecond() {
334        long m1 = getFirstMillisecond();
335        long m2 = getLastMillisecond();
336        return m1 + (m2 - m1) / 2;
337    }
338
339    /**
340     * Returns the millisecond closest to the middle of the time period,
341     * evaluated using the supplied calendar (which incorporates a timezone).
342     *
343     * @param calendar  the calendar.
344     *
345     * @return The middle millisecond.
346     */
347    public long getMiddleMillisecond(Calendar calendar) {
348        long m1 = getFirstMillisecond(calendar);
349        long m2 = getLastMillisecond(calendar);
350        return m1 + (m2 - m1) / 2;
351    }
352
353    /**
354     * Returns the millisecond (relative to the epoch) corresponding to the 
355     * specified {@code anchor} using the supplied {@code calendar} 
356     * (which incorporates a time zone).
357     * 
358     * @param anchor  the anchor ({@code null} not permitted).
359     * @param calendar  the calendar ({@code null} not permitted).
360     * 
361     * @return Milliseconds since the epoch.
362     */
363    public long getMillisecond(TimePeriodAnchor anchor, Calendar calendar) {
364        if (anchor.equals(TimePeriodAnchor.START)) {
365            return getFirstMillisecond(calendar);
366        } else if (anchor.equals(TimePeriodAnchor.MIDDLE)) {
367            return getMiddleMillisecond(calendar);
368        } else if (anchor.equals(TimePeriodAnchor.END)) {
369            return getLastMillisecond(calendar);
370        } else {
371            throw new IllegalStateException("Unrecognised anchor: " + anchor);
372        }
373    }
374    
375    /**
376     * Returns a string representation of the time period.
377     *
378     * @return The string.
379     */
380    @Override
381    public String toString() {
382        return String.valueOf(getStart());
383    }
384
385}