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}