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}