001/*
002 * ObjectLab, http://www.objectlab.co.uk/open is sponsoring the ObjectLab Kit.
003 *
004 * Based in London, we are world leaders in the design and development
005 * of bespoke applications for the securities financing markets.
006 *
007 * <a href="http://www.objectlab.co.uk/open">Click here to learn more</a>
008 *           ___  _     _           _   _          _
009 *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
010 *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
011 *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
012 *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
013 *                   |__/
014 *
015 *                     www.ObjectLab.co.uk
016 *
017 * $Id$
018 *
019 * Copyright 2006 the original author or authors.
020 *
021 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
022 * use this file except in compliance with the License. You may obtain a copy of
023 * the License at
024 *
025 * http://www.apache.org/licenses/LICENSE-2.0
026 *
027 * Unless required by applicable law or agreed to in writing, software
028 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
029 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
030 * License for the specific language governing permissions and limitations under
031 * the License.
032 */
033package net.objectlab.kit.datecalc.common;
034
035import static net.objectlab.kit.datecalc.common.HolidayHandlerType.BACKWARD;
036import static net.objectlab.kit.datecalc.common.HolidayHandlerType.FORWARD;
037import static net.objectlab.kit.datecalc.common.HolidayHandlerType.MODIFIED_FOLLOWING;
038import static net.objectlab.kit.datecalc.common.HolidayHandlerType.MODIFIED_PRECEDING;
039
040import java.io.Serializable;
041import java.util.ArrayList;
042import java.util.HashSet;
043import java.util.List;
044import java.util.Set;
045
046/**
047 * Abstract implementation in order to encapsulate all the common functionality
048 * between Jdk and Joda implementations. It is parameterized on &lt;E&gt;
049 * but basically <code>Date</code> and <code>LocalDate</code> are the only
050 * viable values for it for now.
051 *
052 * @author Marcin Jekot and Benoit Xhenseval
053 *
054 * @param <E>
055 *            a representation of a date, typically JDK: Date, Calendar;
056 *            Joda:LocalDate, YearMonthDay
057 *
058 */
059public abstract class AbstractDateCalculator<E extends Serializable> implements DateCalculator<E> {
060    private static final int MONTHS_IN_YEAR = 12;
061
062    protected static final int DAYS_IN_WEEK = 7;
063
064    private String name;
065
066    private E startDate;
067
068    private E currentBusinessDate;
069
070    private HolidayCalendar<E> holidayCalendar;
071
072    private HolidayHandler<E> holidayHandler;
073
074    private int currentIncrement;
075
076    protected AbstractDateCalculator(final String name, final HolidayCalendar<E> holidayCalendar, final HolidayHandler<E> holidayHandler) {
077        this.name = name;
078        if (holidayCalendar != null) {
079            this.holidayCalendar = new ImmutableHolidayCalendar<>(holidayCalendar);
080        } else {
081            this.holidayCalendar = new ImmutableHolidayCalendar<>(new DefaultHolidayCalendar<E>());
082        }
083        this.holidayHandler = holidayHandler;
084    }
085
086    @Override
087    public DateCalculator<E> setHolidayCalendar(final HolidayCalendar<E> calendar) {
088        if (calendar != null) {
089            if (calendar instanceof ImmutableHolidayCalendar) {
090                holidayCalendar = calendar;
091            } else {
092                holidayCalendar = new ImmutableHolidayCalendar<>(calendar);
093            }
094        } else {
095            holidayCalendar = new ImmutableHolidayCalendar<>(new DefaultHolidayCalendar<E>());
096        }
097        return this;
098    }
099
100    @Override
101    public String getName() {
102        return name;
103    }
104
105    public void setName(final String name) {
106        this.name = name;
107    }
108
109    @Override
110    public E getStartDate() {
111        if (startDate == null) {
112            startDate = getToday();
113        }
114        return startDate;
115    }
116
117    /** Set both start date and current date */
118    @Override
119    public DateCalculator<E> setStartDate(final E startDate) {
120        this.startDate = startDate;
121        setCurrentBusinessDate(startDate);
122        return this;
123    }
124
125    @Override
126    public E getCurrentBusinessDate() {
127        if (currentBusinessDate == null) {
128            currentBusinessDate = getToday();
129        }
130        return currentBusinessDate;
131    }
132
133    /**
134     * move the current date by a given tenor, this means that if a date is
135     * either a 'weekend' or holiday, it will be skipped acording to the holiday
136     * handler and not count towards the number of days to move.
137     *
138     * @param tenor the tenor.
139     * @param spotLag
140     *            number of days to "spot" days, this can vary from one market
141     *            to the other.
142     * @return the current businessCalendar (so one can do
143     *         calendar.moveByTenor(StandardTenor.T_2M).getCurrentBusinessDate();)
144     */
145    @Override
146    public DateCalculator<E> moveByTenor(final Tenor tenor, final int spotLag) {
147        if (tenor == null) {
148            throw new IllegalArgumentException("Tenor cannot be null");
149        }
150
151        TenorCode tenorCode = tenor.getCode();
152        if (tenorCode != TenorCode.OVERNIGHT && tenorCode != TenorCode.TOM_NEXT /*&& spotLag != 0*/) {
153            // get to the Spot date first:
154            moveToSpotDate(spotLag);
155        }
156        int unit = tenor.getUnits();
157        if (tenorCode == TenorCode.WEEK) {
158            tenorCode = TenorCode.DAY;
159            unit *= DAYS_IN_WEEK;
160        }
161
162        if (tenorCode == TenorCode.YEAR) {
163            tenorCode = TenorCode.MONTH;
164            unit *= MONTHS_IN_YEAR;
165        }
166
167        return applyTenor(tenorCode, unit);
168    }
169
170    protected DateCalculator<E> applyTenor(final TenorCode tenorCode, final int unit) {
171        DateCalculator<E> calc;
172        // move by tenor
173        switch (tenorCode) {
174        case OVERNIGHT:
175            calc = moveByDays(1);
176            break;
177        case TOM_NEXT: // it would have NOT moved by
178            moveByDays(1); // calculate Tomorrow
179            calc = moveByDays(1); // then the next!
180            break;
181        case SPOT_NEXT:
182            calc = moveByDays(1);
183            break;
184        case SPOT:
185            calc = this;
186            break;
187        case DAY:
188            calc = moveByDays(unit);
189            break;
190        case MONTH:
191            calc = moveByMonths(unit);
192            break;
193        default:
194            throw new UnsupportedOperationException("Sorry not yet...");
195        }
196        return calc;
197    }
198
199    /**
200     * Move the current date by a given tenor, please note that all tenors are
201     * relative to the CURRENT day (and NOT from spot).
202     *
203     * @param tenor
204     *            the Tenor to reach.
205     * @return the current DateCalculator
206     * @since 1.1.0
207     */
208    @Override
209    public DateCalculator<E> moveByTenor(final Tenor tenor) {
210        return moveByTenor(tenor, 0);
211    }
212
213    /**
214     * Calculate a series of Tenor codes in one go based on current day,
215     * this does NOT change the current business date.
216     *
217     * @return list of dates in same order as tenors.
218     * @since 1.1.0
219     */
220    @Override
221    public List<E> calculateTenorDates(final List<Tenor> tenors) {
222        return calculateTenorDates(tenors, 0);
223    }
224
225    /**
226     * Calculate a series of Tenor codes in one go based on SPOT day (calculated
227     * with the spot lag), this does NOT change the current business date.
228     *
229     * @return list of dates in same order as tenors.
230     * @since 1.1.0
231     */
232    @Override
233    public List<E> calculateTenorDates(final List<Tenor> tenors, final int spotLag) {
234        final List<E> list = new ArrayList<>();
235
236        if (tenors != null) {
237            final E originalDate = clone(getCurrentBusinessDate());
238            for (final Tenor tenor : tenors) {
239                moveByTenor(tenor, spotLag);
240                list.add(getCurrentBusinessDate());
241                setCurrentBusinessDate(originalDate);
242            }
243        }
244
245        return list;
246    }
247
248    // -----------------------------------------------------------------------
249    //
250    // ObjectLab, world leaders in the design and development of bespoke
251    // applications for the securities financing markets.
252    // www.ObjectLab.co.uk
253    //
254    // -----------------------------------------------------------------------
255
256    protected abstract DateCalculator<E> moveByMonths(int months);
257
258    public DateCalculator<E> setHolidayHandler(final HolidayHandler<E> holidayHandler) {
259        this.holidayHandler = holidayHandler;
260        return this;
261    }
262
263    @Override
264    public String getHolidayHandlerType() {
265        return holidayHandler != null ? holidayHandler.getType() : null;
266    }
267
268    /**
269     * is the given date a non working day?
270     */
271    @Override
272    public boolean isNonWorkingDay(final E date) {
273        if (date != null && (holidayCalendar.getEarlyBoundary() != null || holidayCalendar.getLateBoundary() != null)) {
274            checkBoundary(date);
275        }
276        return isWeekend(date) || holidayCalendar.isHoliday(date);
277    }
278
279    /**
280     * This may throw an {@link IndexOutOfBoundsException} if the date is not within the
281     * boundaries.
282     * @param date
283     */
284    protected abstract void checkBoundary(E date);
285
286    @Override
287    public boolean isCurrentDateNonWorking() {
288        if (currentBusinessDate == null) {
289            currentBusinessDate = getToday();
290        }
291        return isNonWorkingDay(currentBusinessDate);
292    }
293
294    @Override
295    public E forceCurrentDateNoAdjustment(final E date) {
296        currentBusinessDate = date;
297        return currentBusinessDate;
298    }
299
300    @Override
301    public E setCurrentBusinessDate(final E date) {
302        currentBusinessDate = date;
303        if (holidayHandler != null && date != null) {
304            currentBusinessDate = holidayHandler.moveCurrentDate(this);
305        }
306        if (date != null && (holidayCalendar.getEarlyBoundary() != null || holidayCalendar.getLateBoundary() != null)) {
307            checkBoundary(date);
308        }
309        return currentBusinessDate;
310    }
311
312    public HolidayHandler<E> getHolidayHandler() {
313        return holidayHandler;
314    }
315
316    protected void moveToSpotDate(final int spotLag) {
317        moveByBusinessDays(spotLag);
318    }
319
320    @Override
321    public DateCalculator<E> moveByBusinessDays(final int businessDays) {
322        checkHolidayValidity(businessDays);
323
324        final int numberOfStepsLeft = Math.abs(businessDays);
325        final int step = businessDays < 0 ? -1 : 1;
326
327        for (int i = 0; i < numberOfStepsLeft; i++) {
328            moveByDays(step);
329        }
330
331        return this;
332    }
333
334    private void checkHolidayValidity(final int businessDays) {
335        if (businessDays > 0 && holidayHandler != null
336                && (holidayHandler.getType().equals(BACKWARD) || holidayHandler.getType().equals(MODIFIED_PRECEDING))) {
337            throw new IllegalArgumentException(
338                    "A " + MODIFIED_PRECEDING + " or " + BACKWARD + " does not allow positive steps for moveByBusinessDays");
339        } else if (businessDays < 0 && holidayHandler != null
340                && (holidayHandler.getType().equals(FORWARD) || holidayHandler.getType().equals(MODIFIED_FOLLOWING))) {
341            throw new IllegalArgumentException(
342                    "A " + MODIFIED_FOLLOWING + " or " + FORWARD + " does not allow negative steps for moveByBusinessDays");
343        }
344    }
345
346    /**
347     * Allows DateCalculators to be combined into a new one, the startDate and
348     * currentBusinessDate will be the ones from the existing calendar (not the
349     * parameter one). The name will be combined name1+"/"+calendar.getName().
350     *
351     * @param calculator
352     *            return the same DateCalculator if calendar is null or the
353     *            original calendar (but why would you want to do that?)
354     * @throws IllegalArgumentException
355     *             if both calendars have different types of HolidayHandlers or
356     *             WorkingWeek;
357     */
358    @Override
359    public DateCalculator<E> combine(final DateCalculator<E> calculator) {
360        if (calculator == null || calculator == this) {
361            return this;
362        }
363
364        checkHolidayHandlerValidity(calculator);
365
366        final Set<E> newSet = new HashSet<>();
367        if (holidayCalendar != null) {
368            newSet.addAll(holidayCalendar.getHolidays());
369        }
370
371        final HolidayCalendar<E> calendarToCombine = calculator.getHolidayCalendar();
372        checkBoundaries(calendarToCombine);
373
374        if (calendarToCombine.getHolidays() != null) {
375            newSet.addAll(calendarToCombine.getHolidays());
376        }
377
378        final HolidayCalendar<E> newCal = new DefaultHolidayCalendar<>(newSet,
379                compareDate(holidayCalendar.getEarlyBoundary(), calendarToCombine.getEarlyBoundary(), false),
380                compareDate(holidayCalendar.getLateBoundary(), calendarToCombine.getLateBoundary(), true));
381
382        return createNewCalculator(getName() + "/" + calculator.getName(), getStartDate(), newCal, holidayHandler);
383    }
384
385    private void checkHolidayHandlerValidity(final DateCalculator<E> calculator) {
386        if (holidayHandler == null && calculator.getHolidayHandlerType() != null
387                || holidayHandler != null && !holidayHandler.getType().equals(calculator.getHolidayHandlerType())) {
388            throw new IllegalArgumentException("Combined Calendars cannot have different handler types");
389        }
390    }
391
392    private void checkBoundaries(final HolidayCalendar<E> calendarToCombine) {
393        if (calendarToCombine.getEarlyBoundary() != null && holidayCalendar.getEarlyBoundary() == null
394                || calendarToCombine.getEarlyBoundary() == null && holidayCalendar.getEarlyBoundary() != null) {
395            throw new IllegalArgumentException("Both Calendar to be combined must either have each Early boundaries or None.");
396        }
397
398        if (calendarToCombine.getLateBoundary() != null && holidayCalendar.getLateBoundary() == null
399                || calendarToCombine.getLateBoundary() == null && holidayCalendar.getLateBoundary() != null) {
400            throw new IllegalArgumentException("Both Calendar to be combined must either have each Late boundaries or None.");
401        }
402    }
403
404    protected abstract E getToday();
405
406    protected abstract E compareDate(E date1, E date2, boolean returnEarliest);
407
408    protected abstract DateCalculator<E> createNewCalculator(String calcName, E theStartDate, HolidayCalendar<E> holidays, HolidayHandler<E> handler);
409
410    /**
411     * @return Returns the currentIncrement.
412     */
413    @Override
414    public int getCurrentIncrement() {
415        return currentIncrement;
416    }
417
418    /**
419     * @param currentIncrement The currentIncrement to set.
420     */
421    @Override
422    public DateCalculator<E> setCurrentIncrement(final int currentIncrement) {
423        this.currentIncrement = currentIncrement;
424        return this;
425    }
426
427    /**
428     * @return Returns the holidayCalendar.
429     */
430    @Override
431    public HolidayCalendar<E> getHolidayCalendar() {
432        return holidayCalendar;
433    }
434
435    protected abstract E clone(final E date);
436}
437
438/*
439 * ObjectLab, http://www.objectlab.co.uk/open is sponsoring the ObjectLab Kit.
440 *
441 * Based in London, we are world leaders in the design and development
442 * of bespoke applications for the securities financing markets.
443 *
444 * <a href="http://www.objectlab.co.uk/open">Click here to learn more about us</a>
445 *           ___  _     _           _   _          _
446 *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
447 *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
448 *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
449 *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
450 *                   |__/
451 *
452 *                     www.ObjectLab.co.uk
453 */