001/*
002 * Shredzone Commons - suncalc
003 *
004 * Copyright (C) 2017 Richard "Shred" Körber
005 *   http://commons.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.commons.suncalc.util;
015
016import static java.lang.Math.*;
017
018import java.util.Calendar;
019import java.util.Date;
020import java.util.TimeZone;
021
022import org.shredzone.commons.suncalc.param.GenericParameter;
023import org.shredzone.commons.suncalc.param.LocationParameter;
024import org.shredzone.commons.suncalc.param.TimeParameter;
025import org.shredzone.commons.suncalc.param.TimeResultParameter;
026
027/**
028 * A base implementation of {@link LocationParameter} and {@link TimeParameter}.
029 * <p>
030 * For internal use only.
031 *
032 * @param <T>
033 *            Type of the final builder
034 */
035@SuppressWarnings("unchecked")
036public class BaseBuilder<T> implements GenericParameter<T>, LocationParameter<T>,
037        TimeParameter<T>, TimeResultParameter<T>, Cloneable {
038
039    private double lat = 0.0;
040    private double lng = 0.0;
041    private double height = 0.0;
042    private Calendar cal = createCalendar();
043    private Unit unit = Unit.MINUTES;
044
045    @Override
046    public T on(int year, int month, int date) {
047        cal.clear();
048        cal.set(year, month - 1, date);
049        return (T) this;
050    }
051
052    @Override
053    public T on(int year, int month, int date, int hour, int minute, int second) {
054        cal.clear();
055        cal.set(year, month - 1, date, hour, minute, second);
056        return (T) this;
057    }
058
059    @Override
060    public T on(Date date) {
061        if (date == null) { //NOSONAR: safety null check
062            throw new NullPointerException();
063        }
064        cal.setTime(date);
065        return (T) this;
066    }
067
068    @Override
069    public T on(Calendar calendar) {
070        if (calendar == null) { //NOSONAR: safety null check
071            throw new NullPointerException();
072        }
073        on(calendar.getTime());
074        timezone(calendar.getTimeZone());
075        return (T) this;
076    }
077
078    @Override
079    public T plusDays(int days) {
080        cal.add(Calendar.DAY_OF_MONTH, days);
081        return (T) this;
082    }
083
084    @Override
085    public T today() {
086        now();
087        midnight();
088        return (T) this;
089    }
090
091    @Override
092    public T tomorrow() {
093        today();
094        plusDays(1);
095        return (T) this;
096    }
097
098    @Override
099    public T now() {
100        return on(createCalendar());
101    }
102
103    @Override
104    public T midnight() {
105        cal.set(Calendar.HOUR_OF_DAY, 0);
106        cal.set(Calendar.MINUTE, 0);
107        cal.set(Calendar.SECOND, 0);
108        cal.set(Calendar.MILLISECOND, 0);
109        return (T) this;
110    }
111
112    @Override
113    public T timezone(TimeZone tz) {
114        if (tz == null) { //NOSONAR: safety null check
115            throw new NullPointerException();
116        }
117        cal.setTimeZone(tz);
118        return (T) this;
119    }
120
121    @Override
122    public T timezone(String id) {
123        return timezone(TimeZone.getTimeZone(id));
124    }
125
126    @Override
127    public T utc() {
128        return timezone("UTC");
129    }
130
131    @Override
132    public T localTime() {
133        return timezone(TimeZone.getDefault());
134    }
135
136    @Override
137    public T at(double lat, double lng) {
138        latitude(lat);
139        longitude(lng);
140        return (T) this;
141    }
142
143    @Override
144    public T at(double[] coords) {
145        if (coords.length != 2 && coords.length != 3) {
146            throw new IllegalArgumentException("Array must contain 2 or 3 doubles");
147        }
148        if (coords.length == 3) {
149            height(coords[2]);
150        }
151        return at(coords[0], coords[1]);
152    }
153
154    @Override
155    public T latitude(double lat) {
156        if (lat < -90.0 || lat > 90.0) {
157            throw new IllegalArgumentException("Latitude out of range, -90.0 <= " + lat + " <= 90.0");
158        }
159        this.lat = lat;
160        return (T) this;
161    }
162
163    @Override
164    public T longitude(double lng) {
165        if (lng < -180.0 || lng > 180.0) {
166            throw new IllegalArgumentException("Longitude out of range, -180.0 <= " + lng + " <= 180.0");
167        }
168        this.lng = lng;
169        return (T) this;
170    }
171
172    @Override
173    public T latitude(int d, int m, double s) {
174        return latitude(dms(d, m, s));
175    }
176
177    @Override
178    public T longitude(int d, int m, double s) {
179        return longitude(dms(d, m, s));
180    }
181
182    @Override
183    public T height(double h) {
184        this.height = max(h, 0.0);
185        return (T) this;
186    }
187
188    @Override
189    public T truncatedTo(Unit unit) {
190        if (unit == null) { //NOSONAR: safety null check
191            throw new NullPointerException();
192        }
193        this.unit = unit;
194        return (T) this;
195    }
196
197    @Override
198    public T sameTimeAs(TimeParameter<?> t) {
199        if (! (t instanceof BaseBuilder)) {
200            throw new IllegalArgumentException("Cannot read the TimeParameter");
201        }
202        this.cal = (Calendar) ((BaseBuilder<?>) t).cal.clone();
203        return (T) this;
204    }
205
206    @Override
207    public T sameLocationAs(LocationParameter<?> l) {
208        if (! (l instanceof BaseBuilder)) {
209            throw new IllegalArgumentException("Cannot read the LocationParameter");
210        }
211        BaseBuilder<?> origin = (BaseBuilder<?>) l;
212        this.lat = origin.lat;
213        this.lng = origin.lng;
214        this.height = origin.height;
215        return (T) this;
216    }
217
218    @Override
219    public T copy() {
220        try {
221            return (T) clone();
222        } catch (CloneNotSupportedException ex) {
223            throw new RuntimeException(ex); // Should never be thrown anyway
224        }
225    }
226
227    /**
228     * Returns the longitude.
229     *
230     * @return Longitude, in degrees.
231     */
232    public double getLongitude() {
233        return lng;
234    }
235
236    /**
237     * Returns the latitude.
238     *
239     * @return Latitude, in degrees.
240     */
241    public double getLatitude() {
242        return lat;
243    }
244
245    /**
246     * Returns the longitude.
247     *
248     * @return Longitude, in radians.
249     */
250    public double getLongitudeRad() {
251        return toRadians(lng);
252    }
253
254    /**
255     * Returns the latitude.
256     *
257     * @return Latitude, in radians.
258     */
259    public double getLatitudeRad() {
260        return toRadians(lat);
261    }
262
263    /**
264     * Returns the height, in meters above sea level.
265     *
266     * @return Height, meters above sea level
267     */
268    public double getHeight() {
269        return height;
270    }
271
272    /**
273     * Returns the {@link JulianDate} to be used.
274     *
275     * @return {@link JulianDate}
276     */
277    public JulianDate getJulianDate() {
278        return new JulianDate((Calendar) cal.clone());
279    }
280
281    /**
282     * Returns the {@link Unit} to truncate to.
283     *
284     * @return {@link Unit}
285     * @since 2.3
286     */
287    public Unit getTruncatedTo() {
288        return unit;
289    }
290
291    /**
292     * Creates a new {@link Calendar} instance containing the current instant.
293     * <p>
294     * This method can be overriden on unit tests.
295     *
296     * @return {@link Calendar} instance
297     */
298    protected Calendar createCalendar() {
299        return Calendar.getInstance();
300    }
301
302    /**
303     * Converts dms to double.
304     *
305     * @param d
306     *            Degrees. Sign is used for result.
307     * @param m
308     *            Minutes. Sign is ignored.
309     * @param s
310     *            Seconds and fractions. Sign is ignored.
311     * @return angle, in degrees
312     */
313    private static double dms(int d, int m, double s) {
314        double sig = d < 0 ? -1.0 : 1.0;
315        return sig * ((abs(s) / 60.0 + abs(m)) / 60.0 + abs(d));
316    }
317
318    @Override
319    protected Object clone() throws CloneNotSupportedException {
320        BaseBuilder<T> b = (BaseBuilder<T>) super.clone();
321        b.cal = (Calendar) this.cal.clone();
322        return b;
323    }
324
325}