001package net.objectlab.kit.datecalc.common;
002
003import java.io.Serializable;
004
005import net.objectlab.kit.datecalc.common.ccy.CurrencyCalculatorConfig;
006
007/**
008 * Provides enough information to create an immutable CurrencyDateCalculator.
009 *
010 * <pre>
011    CurrencyDateCalculatorBuilder&lt;LocalDate&gt; builder = new CurrencyDateCalculatorBuilder&lt;LocalDate&gt;() //
012            .currencyPair("EUR", "GBP", SpotLag.T_2) //
013            .ccy1Calendar(new DefaultHolidayCalendar&lt;LocalDate&gt;()) // empty
014            .ccy1Week(WorkingWeek.DEFAULT) // Mon-Fri
015            .ccy2Calendar(gbpCalendar) //
016            .ccy2Week(WorkingWeek.DEFAULT) // Mon-Fri
017            .crossCcy("USD") // the usual suspect
018            .crossCcyCalendar(usdCalendar) //
019            .crossCcyWeek(WorkingWeek.DEFAULT) // Mon-Fri;
020            .adjustStartDateWithCurrencyPair(true) // default is true, Move the startDate to a working date for ccy1 and ccy2
021            .tenorHolidayHandler(new LocalDateForwardHandler()) // Forward (or equivalent for your implementation)
022            .brokenDateAllowed(false) // use the CrossCcy holidays on Spot and Tenor Date
023            .currencyCalculatorConfig(new DefaultCurrencyCalculatorConfig()) // Will be used for finding Working Weeks IF NOT PROVIDED and Latin
024                                                                             // American ccy USD handling.
025 * </pre>
026 *
027 * @param <E> JDK Date/Calendar, JDK8 LocalDate or Joda LocalDate
028 * @since 1.4.0
029 */
030public class CurrencyDateCalculatorBuilder<E extends Serializable> {
031    private String ccy1;
032    private String ccy2;
033    private String crossCcy = CalculatorConstants.USD_CODE;
034    private HolidayCalendar<E> ccy1Calendar = new DefaultHolidayCalendar<>();
035    private HolidayCalendar<E> ccy2Calendar = new DefaultHolidayCalendar<>();
036    private HolidayCalendar<E> crossCcyCalendar = new DefaultHolidayCalendar<>();
037    private HolidayHandler<E> tenorHolidayHandler;
038    private WorkingWeek ccy1Week;
039    private WorkingWeek ccy2Week;
040    private WorkingWeek crossCcyWeek;
041    private CurrencyCalculatorConfig currencyCalculatorConfig;
042    private boolean brokenDateAllowed;
043    private boolean adjustStartDateWithCurrencyPair = true;
044    private SpotLag spotLag = SpotLag.T_2;
045
046    /**
047     * Default values are:
048     * <ul>
049     * <li>crossCcy = USD</li>
050     * <li>ccy1Calendar = Empty Calendar</li>
051     * <li>ccy2Calendar = Empty Calendar</li>
052     * <li>crossCcyCalendar = Empty Calendar</li>
053     * <li>brokenDateAllowed = false</li>
054     * <li>adjustStartDateWithCurrencyPair = true</li>
055     * <li>spotLag = SpotLag.T_2</li>
056     * </ul>
057     */
058    public CurrencyDateCalculatorBuilder() {
059        // using default values ok
060    }
061
062    /**
063     * Checks the builder and throws an IllegalArgumentException if there are issues e.g.
064     * <ul>
065     * <li>ccy1 or ccy2 missing</li>
066     * <li>ccy1Week or ccy2Week missing</li>
067     * <li>spotLag is missing</li>
068     * <li>tenorHolidayHandler is missing</li>
069     * <li>If brokenDate is not allowed, we need crossCcy, crossCcyWeek and crossCcyCalendar.</li>
070     * </ul>
071     */
072    public void checkValidity() {
073        StringBuilder b = new StringBuilder();
074        if (ccy1 == null || ccy1.length() == 0) {
075            b.append("ccy1 is required");
076        }
077        if (ccy2 == null || ccy2.length() == 0) {
078            append(b, "ccy2 is required");
079        }
080        if (getCcy1Week() == null) {
081            append(b, "ccy1Week is required");
082        }
083        if (getCcy2Week() == null) {
084            append(b, "ccy2Week is required");
085        }
086        if (!brokenDateAllowed) {
087            checkBrokenDate(b);
088        }
089        if (spotLag == null) {
090            append(b, "spotLag is required");
091        }
092        if (tenorHolidayHandler == null) {
093            append(b, "tenorHolidayHandler is required");
094        }
095        if (b.length() > 0) {
096            throw new IllegalArgumentException(b.toString());
097        }
098    }
099
100    private void checkBrokenDate(StringBuilder b) {
101        if (getCrossCcy() == null) {
102            append(b, "crossCcy is required");
103        }
104        if (getCrossCcyWeek() == null) {
105            append(b, "crossCcyWeek is required");
106        }
107        if (getCrossCcyCalendar() == null) {
108            append(b, "crossCcyCalendar is required");
109        }
110    }
111
112    private static void append(StringBuilder b, String string) {
113        if (b.length() > 0) {
114            b.append(",");
115        }
116        b.append(string);
117    }
118
119    public String getCcy1() {
120        return ccy1;
121    }
122
123    public String getCcy2() {
124        return ccy2;
125    }
126
127    public String getCrossCcy() {
128        return crossCcy;
129    }
130
131    public HolidayCalendar<E> getCcy1Calendar() {
132        return ccy1Calendar;
133    }
134
135    public HolidayCalendar<E> getCcy2Calendar() {
136        return ccy2Calendar;
137    }
138
139    public HolidayCalendar<E> getCrossCcyCalendar() {
140        return crossCcyCalendar;
141    }
142
143    public HolidayHandler<E> getTenorHolidayHandler() {
144        return tenorHolidayHandler;
145    }
146
147    public WorkingWeek getCcy1Week() {
148        if (ccy1Week != null) {
149            return ccy1Week;
150        }
151        return currencyCalculatorConfig != null ? currencyCalculatorConfig.getWorkingWeek(ccy1) : null;
152    }
153
154    public WorkingWeek getCcy2Week() {
155        if (ccy2Week != null) {
156            return ccy2Week;
157        }
158        return currencyCalculatorConfig != null ? currencyCalculatorConfig.getWorkingWeek(ccy2) : null;
159    }
160
161    public WorkingWeek getCrossCcyWeek() {
162        if (crossCcyWeek != null) {
163            return crossCcyWeek;
164        }
165        return currencyCalculatorConfig != null ? currencyCalculatorConfig.getWorkingWeek(crossCcy) : null;
166    }
167
168    public CurrencyCalculatorConfig getCurrencyCalculatorConfig() {
169        return currencyCalculatorConfig;
170    }
171
172    public boolean isBrokenDateAllowed() {
173        return brokenDateAllowed;
174    }
175
176    /**
177     * This specialises the calculator to the given currency pair and the SpotLag (0, 1, 2). Given than some currencies can have different
178     * SpotLag depending on the business, this is something that the user will have to provide.
179     * @param ccy1
180     * @param ccy2
181     * @param spotLag
182     * @return the builder
183     */
184    public CurrencyDateCalculatorBuilder<E> currencyPair(final String ccy1, final String ccy2, final SpotLag spotLag) {
185        this.ccy1 = ccy1;
186        this.ccy2 = ccy2;
187        this.spotLag = spotLag;
188        return this;
189    }
190
191    public SpotLag getSpotLag() {
192        return spotLag;
193    }
194
195    public boolean isAdjustStartDateWithCurrencyPair() {
196        return adjustStartDateWithCurrencyPair;
197    }
198
199    /**
200     * If true, the startDate given to the calculator will be move to the NEXT working day for both currencies (e.g. it will skip weekends
201     * and any holidays).
202     * @param adjustStartDateWithCurrencyPair default true
203     * @return the builder
204     */
205    public CurrencyDateCalculatorBuilder<E> adjustStartDateWithCurrencyPair(final boolean adjustStartDateWithCurrencyPair) {
206        this.adjustStartDateWithCurrencyPair = adjustStartDateWithCurrencyPair;
207        return this;
208    }
209
210    /**
211     * If true, then the calculator can return a SpotDate/TenorDate where the cross currency is NOT a trading date (e.g. July 4 for EUR/GBP which
212     * usually would be skipped).
213     * @param brokenDateAllowed default false
214     * @return the builder
215     */
216    public CurrencyDateCalculatorBuilder<E> brokenDateAllowed(final boolean brokenDateAllowed) {
217        this.brokenDateAllowed = brokenDateAllowed;
218        return this;
219    }
220
221    /**
222     * Provides information about currencies subject to USD on T+1 and WorkingWeeks if not specified individually.
223     * @param currencyCalculatorConfig the config
224     * @return
225     */
226    public CurrencyDateCalculatorBuilder<E> currencyCalculatorConfig(final CurrencyCalculatorConfig currencyCalculatorConfig) {
227        this.currencyCalculatorConfig = currencyCalculatorConfig;
228        return this;
229    }
230
231    /**
232     * The holiday calendar for ccy1, if null or not set, then a default calendar will be used with NO holidays.
233     * @param ccy1Calendar the Calendar for ccy1
234     * @return the builder
235     */
236    public CurrencyDateCalculatorBuilder<E> ccy1Calendar(final HolidayCalendar<E> ccy1Calendar) {
237        if (ccy1Calendar != null) {
238            this.ccy1Calendar = ccy1Calendar;
239        }
240        return this;
241    }
242
243    /**
244     * The holiday calendar for ccy2, if null or not set, then a default calendar will be used with NO holidays.
245     * @param ccy2Calendar the Calendar for ccy2
246     * @return the builder
247     */
248    public CurrencyDateCalculatorBuilder<E> ccy2Calendar(final HolidayCalendar<E> ccy2Calendar) {
249        if (ccy2Calendar != null) {
250            this.ccy2Calendar = ccy2Calendar;
251        }
252        return this;
253    }
254
255    /**
256     * If brokenDate is not allowed, we do require to check the WorkingWeek and Holiday for the crossCcy when
257     * validating the SpotDate or a Tenor date; if null or not set, then a default calendar will be used with NO holidays.
258     * @param crossCcy the crossCcy (default USD).
259     * @return the builder
260     */
261    public CurrencyDateCalculatorBuilder<E> crossCcy(final String crossCcy) {
262        this.crossCcy = crossCcy;
263        return this;
264    }
265
266    /**
267     * If brokenDate is not allowed, we do require to check the WorkingWeek and Holiday for the crossCcy when
268     * validating the SpotDate or a Tenor date.
269     * @param crossCcyCalendar the set of holidays for the crossCcy
270     * @return the builder
271     */
272    public CurrencyDateCalculatorBuilder<E> crossCcyCalendar(final HolidayCalendar<E> crossCcyCalendar) {
273        if (crossCcyCalendar != null) {
274            this.crossCcyCalendar = crossCcyCalendar;
275        }
276        return this;
277    }
278
279    /**
280     * Provides the holiday handler for the Tenor Date, note that Spot is ALWAYS using Forward.
281     * @param holidayHandler the Handler to work out what to do if a Tenor Date falls on a non WorkingDay.
282     * @return the builder
283     */
284    public CurrencyDateCalculatorBuilder<E> tenorHolidayHandler(final HolidayHandler<E> holidayHandler) {
285        this.tenorHolidayHandler = holidayHandler;
286        return this;
287    }
288
289    /**
290     * Provides the definition of a working week for the currency; if not provided and the currencyCalculatorConfig is given, it 
291     * will do a look up for this currency.
292     * @param ccy1Week WorkingWeek definition
293     * @return the builder
294     */
295    public CurrencyDateCalculatorBuilder<E> ccy1Week(final WorkingWeek ccy1Week) {
296        this.ccy1Week = ccy1Week;
297        return this;
298    }
299
300    /**
301     * Provides the definition of a working week for the currency; if not provided and the currencyCalculatorConfig is given, it 
302     * will do a look up for this currency.
303     * @param ccy2Week WorkingWeek definition
304     * @return the builder
305     */
306    public CurrencyDateCalculatorBuilder<E> ccy2Week(final WorkingWeek ccy2Week) {
307        this.ccy2Week = ccy2Week;
308        return this;
309    }
310
311    /**
312     * If brokenDate is not allowed, we do require to check the WorkingWeek and Holiday for the crossCcy when
313     * validating the SpotDate or a Tenor date.
314     * @param crossCcyWeek the crossCcy WorkingWeek.
315     * @return the builder
316     */
317    public CurrencyDateCalculatorBuilder<E> crossCcyWeek(final WorkingWeek crossCcyWeek) {
318        this.crossCcyWeek = crossCcyWeek;
319        return this;
320    }
321}