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