001/*
002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos
003 *
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions are met:
008 *
009 *  * Redistributions of source code must retain the above copyright notice,
010 *    this list of conditions and the following disclaimer.
011 *
012 *  * Redistributions in binary form must reproduce the above copyright notice,
013 *    this list of conditions and the following disclaimer in the documentation
014 *    and/or other materials provided with the distribution.
015 *
016 *  * Neither the name of JSR-310 nor the names of its contributors
017 *    may be used to endorse or promote products derived from this software
018 *    without specific prior written permission.
019 *
020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031 */
032package org.threeten.bp.format;
033
034import static org.threeten.bp.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH;
035import static org.threeten.bp.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR;
036import static org.threeten.bp.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH;
037import static org.threeten.bp.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR;
038import static org.threeten.bp.temporal.ChronoField.AMPM_OF_DAY;
039import static org.threeten.bp.temporal.ChronoField.CLOCK_HOUR_OF_AMPM;
040import static org.threeten.bp.temporal.ChronoField.CLOCK_HOUR_OF_DAY;
041import static org.threeten.bp.temporal.ChronoField.DAY_OF_MONTH;
042import static org.threeten.bp.temporal.ChronoField.DAY_OF_WEEK;
043import static org.threeten.bp.temporal.ChronoField.DAY_OF_YEAR;
044import static org.threeten.bp.temporal.ChronoField.EPOCH_DAY;
045import static org.threeten.bp.temporal.ChronoField.EPOCH_MONTH;
046import static org.threeten.bp.temporal.ChronoField.HOUR_OF_AMPM;
047import static org.threeten.bp.temporal.ChronoField.HOUR_OF_DAY;
048import static org.threeten.bp.temporal.ChronoField.INSTANT_SECONDS;
049import static org.threeten.bp.temporal.ChronoField.MICRO_OF_DAY;
050import static org.threeten.bp.temporal.ChronoField.MICRO_OF_SECOND;
051import static org.threeten.bp.temporal.ChronoField.MILLI_OF_DAY;
052import static org.threeten.bp.temporal.ChronoField.MILLI_OF_SECOND;
053import static org.threeten.bp.temporal.ChronoField.MINUTE_OF_DAY;
054import static org.threeten.bp.temporal.ChronoField.MINUTE_OF_HOUR;
055import static org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR;
056import static org.threeten.bp.temporal.ChronoField.NANO_OF_DAY;
057import static org.threeten.bp.temporal.ChronoField.NANO_OF_SECOND;
058import static org.threeten.bp.temporal.ChronoField.OFFSET_SECONDS;
059import static org.threeten.bp.temporal.ChronoField.SECOND_OF_DAY;
060import static org.threeten.bp.temporal.ChronoField.SECOND_OF_MINUTE;
061import static org.threeten.bp.temporal.ChronoField.YEAR;
062import static org.threeten.bp.temporal.TemporalAdjusters.nextOrSame;
063
064import java.lang.reflect.Method;
065import java.util.ArrayList;
066import java.util.EnumMap;
067import java.util.HashMap;
068import java.util.HashSet;
069import java.util.LinkedHashMap;
070import java.util.List;
071import java.util.Map;
072import java.util.Map.Entry;
073import java.util.Objects;
074import java.util.Set;
075
076import org.threeten.bp.DateTimeException;
077import org.threeten.bp.DayOfWeek;
078import org.threeten.bp.Instant;
079import org.threeten.bp.LocalDate;
080import org.threeten.bp.LocalTime;
081import org.threeten.bp.ZoneId;
082import org.threeten.bp.ZoneOffset;
083import org.threeten.bp.jdk8.DefaultInterfaceTemporalAccessor;
084import org.threeten.bp.jdk8.Jdk8Methods;
085import org.threeten.bp.temporal.Chrono;
086import org.threeten.bp.temporal.ChronoField;
087import org.threeten.bp.temporal.TemporalAccessor;
088import org.threeten.bp.temporal.TemporalField;
089import org.threeten.bp.temporal.TemporalQueries;
090import org.threeten.bp.temporal.TemporalQuery;
091
092/**
093 * Builder that can holds date and time fields and related date and time objects.
094 * <p>
095 * The builder is used to hold onto different elements of date and time.
096 * It is designed as two separate maps:
097 * <p><ul>
098 * <li>from {@link TemporalField} to {@code long} value, where the value may be
099 * outside the valid range for the field
100 * <li>from {@code Class} to {@link TemporalAccessor}, holding larger scale objects
101 * like {@code LocalDateTime}.
102 * </ul><p>
103 *
104 * <h3>Specification for implementors</h3>
105 * This class is mutable and not thread-safe.
106 * It should only be used from a single thread.
107 */
108public final class DateTimeBuilder
109        extends DefaultInterfaceTemporalAccessor
110        implements TemporalAccessor, Cloneable {
111
112    /**
113     * The map of other fields.
114     */
115    private Map<TemporalField, Long> otherFields;
116    /**
117     * The map of date-time fields.
118     */
119    private final EnumMap<ChronoField, Long> standardFields = new EnumMap<ChronoField, Long>(ChronoField.class);
120    /**
121     * The list of complete date-time objects.
122     */
123    private final List<Object> objects = new ArrayList<>(2);
124
125    //-----------------------------------------------------------------------
126    /**
127     * Creates an empty instance of the builder.
128     */
129    public DateTimeBuilder() {
130    }
131
132    /**
133     * Creates a new instance of the builder with a single field-value.
134     * <p>
135     * This is equivalent to using {@link #addFieldValue(TemporalField, long)} on an empty builder.
136     *
137     * @param field  the field to add, not null
138     * @param value  the value to add, not null
139     */
140    public DateTimeBuilder(TemporalField field, long value) {
141        addFieldValue(field, value);
142    }
143
144    /**
145     * Creates a new instance of the builder.
146     *
147     * @param zone  the zone, may be null
148     * @param chrono  the chronology, may be null
149     */
150    public DateTimeBuilder(ZoneId zone, Chrono<?> chrono) {
151        if (zone != null) {
152            objects.add(zone);
153        }
154        if (chrono != null) {
155            objects.add(chrono);
156        }
157    }
158
159    //-----------------------------------------------------------------------
160    /**
161     * Gets the map of field-value pairs in the builder.
162     *
163     * @return a modifiable copy of the field-value map, not null
164     */
165    public Map<TemporalField, Long> getFieldValueMap() {
166        Map<TemporalField, Long> map = new HashMap<TemporalField, Long>(standardFields);
167        if (otherFields != null) {
168            map.putAll(otherFields);
169        }
170        return map;
171    }
172
173    /**
174     * Checks whether the specified field is present in the builder.
175     *
176     * @param field  the field to find in the field-value map, not null
177     * @return true if the field is present
178     */
179    public boolean containsFieldValue(TemporalField field) {
180        Objects.requireNonNull(field, "field");
181        return standardFields.containsKey(field) || (otherFields != null && otherFields.containsKey(field));
182    }
183
184    /**
185     * Gets the value of the specified field from the builder.
186     *
187     * @param field  the field to query in the field-value map, not null
188     * @return the value of the field, may be out of range
189     * @throws DateTimeException if the field is not present
190     */
191    public long getFieldValue(TemporalField field) {
192        Objects.requireNonNull(field, "field");
193        Long value = getFieldValue0(field);
194        if (value == null) {
195            throw new DateTimeException("Field not found: " + field);
196        }
197        return value;
198    }
199
200    private Long getFieldValue0(TemporalField field) {
201        if (field instanceof ChronoField) {
202            return standardFields.get(field);
203        } else if (otherFields != null) {
204            return otherFields.get(field);
205        }
206        return null;
207    }
208
209    /**
210     * Gets the value of the specified field from the builder ensuring it is valid.
211     *
212     * @param field  the field to query in the field-value map, not null
213     * @return the value of the field, may be out of range
214     * @throws DateTimeException if the field is not present
215     */
216    public long getValidFieldValue(TemporalField field) {
217        long value = getFieldValue(field);
218        return field.range().checkValidValue(value, field);
219    }
220
221    /**
222     * Adds a field-value pair to the builder.
223     * <p>
224     * This adds a field to the builder.
225     * If the field is not already present, then the field-value pair is added to the map.
226     * If the field is already present and it has the same value as that specified, no action occurs.
227     * If the field is already present and it has a different value to that specified, then
228     * an exception is thrown.
229     *
230     * @param field  the field to add, not null
231     * @param value  the value to add, not null
232     * @return {@code this}, for method chaining
233     * @throws DateTimeException if the field is already present with a different value
234     */
235    public DateTimeBuilder addFieldValue(TemporalField field, long value) {
236        Objects.requireNonNull(field, "field");
237        Long old = getFieldValue0(field);  // check first for better error message
238        if (old != null && old.longValue() != value) {
239            throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value + ": " + this);
240        }
241        return putFieldValue0(field, value);
242    }
243
244    private DateTimeBuilder putFieldValue0(TemporalField field, long value) {
245        if (field instanceof ChronoField) {
246            standardFields.put((ChronoField) field, value);
247        } else {
248            if (otherFields == null) {
249                otherFields = new LinkedHashMap<TemporalField, Long>();
250            }
251            otherFields.put(field, value);
252        }
253        return this;
254    }
255
256    /**
257     * Removes a field-value pair from the builder.
258     * <p>
259     * This removes a field, which must exist, from the builder.
260     * See {@link #removeFieldValues(TemporalField...)} for a version which does not throw an exception
261     *
262     * @param field  the field to remove, not null
263     * @return the previous value of the field
264     * @throws DateTimeException if the field is not found
265     */
266    public long removeFieldValue(TemporalField field) {
267        Objects.requireNonNull(field, "field");
268        Long value = null;
269        if (field instanceof ChronoField) {
270            value = standardFields.remove(field);
271        } else if (otherFields != null) {
272            value = otherFields.remove(field);
273        }
274        if (value == null) {
275            throw new DateTimeException("Field not found: " + field);
276        }
277        return value;
278    }
279
280    //-----------------------------------------------------------------------
281    /**
282     * Removes a list of fields from the builder.
283     * <p>
284     * This removes the specified fields from the builder.
285     * No exception is thrown if the fields are not present.
286     *
287     * @param fields  the fields to remove, not null
288     */
289    public void removeFieldValues(TemporalField... fields) {
290        for (TemporalField field : fields) {
291            if (field instanceof ChronoField) {
292                standardFields.remove(field);
293            } else if (otherFields != null) {
294                otherFields.remove(field);
295            }
296        }
297    }
298
299    /**
300     * Queries a list of fields from the builder.
301     * <p>
302     * This gets the value of the specified fields from the builder into
303     * an array where the positions match the order of the fields.
304     * If a field is not present, the array will contain null in that position.
305     *
306     * @param fields  the fields to query, not null
307     * @return the array of field values, not null
308     */
309    public Long[] queryFieldValues(TemporalField... fields) {
310        Long[] values = new Long[fields.length];
311        int i = 0;
312        for (TemporalField field : fields) {
313            values[i++] = getFieldValue0(field);
314        }
315        return values;
316    }
317
318    //-----------------------------------------------------------------------
319    /**
320     * Gets the list of date-time objects in the builder.
321     * <p>
322     * This map is intended for use with {@link ZoneOffset} and {@link ZoneId}.
323     * The returned map is live and may be edited.
324     *
325     * @return the editable list of date-time objects, not null
326     */
327    public List<Object> getCalendricalList() {
328        return objects;
329    }
330
331    /**
332     * Adds a date-time object to the builder.
333     * <p>
334     * This adds a date-time object to the builder.
335     * If the object is a {@code DateTimeBuilder}, each field is added using {@link #addFieldValue}.
336     * If the object is not already present, then the object is added.
337     * If the object is already present and it is equal to that specified, no action occurs.
338     * If the object is already present and it is not equal to that specified, then an exception is thrown.
339     *
340     * @param object  the object to add, not null
341     * @return {@code this}, for method chaining
342     * @throws DateTimeException if the field is already present with a different value
343     */
344    public DateTimeBuilder addCalendrical(Object object) {
345        Objects.requireNonNull(object, "object");
346        // special case
347        if (object instanceof DateTimeBuilder) {
348            DateTimeBuilder dtb = (DateTimeBuilder) object;
349            for (TemporalField field : dtb.getFieldValueMap().keySet()) {
350                addFieldValue(field, dtb.getFieldValue(field));
351            }
352            return this;
353        }
354        if (object instanceof Instant) {
355            addFieldValue(INSTANT_SECONDS, ((Instant) object).getEpochSecond());
356            addFieldValue(NANO_OF_SECOND, ((Instant) object).getNano());
357        } else {
358            objects.add(object);
359        }
360//      TODO
361//        // preserve state of builder until validated
362//        Class<?> cls = dateTime.extract(Class.class);
363//        if (cls == null) {
364//            throw new DateTimeException("Invalid dateTime, unable to extract Class");
365//        }
366//        Object obj = objects.get(cls);
367//        if (obj != null) {
368//            if (obj.equals(dateTime) == false) {
369//                throw new DateTimeException("Conflict found: " + dateTime.getClass().getSimpleName() + " " + obj + " differs from " + dateTime + ": " + this);
370//            }
371//        } else {
372//            objects.put(cls, dateTime);
373//        }
374        return this;
375    }
376
377    //-----------------------------------------------------------------------
378    /**
379     * Resolves the builder, evaluating the date and time.
380     * <p>
381     * This examines the contents of the builder and resolves it to produce the best
382     * available date and time, throwing an exception if a problem occurs.
383     * Calling this method changes the state of the builder.
384     *
385     * @return {@code this}, for method chaining
386     */
387    public DateTimeBuilder resolve() {
388        splitObjects();
389        // handle unusual fields
390        if (otherFields != null) {
391            outer:
392            while (true) {
393                Set<Entry<TemporalField, Long>> entrySet = new HashSet<>(otherFields.entrySet());
394                for (Entry<TemporalField, Long> entry : entrySet) {
395                    if (entry.getKey().resolve(this, entry.getValue())) {
396                        continue outer;
397                    }
398                }
399                break;
400            }
401        }
402        // handle standard fields
403        mergeDate();
404        mergeTime();
405        // TODO: cross validate remaining fields?
406        return this;
407    }
408
409    private void mergeDate() {
410        if (standardFields.containsKey(EPOCH_DAY)) {
411            checkDate(LocalDate.ofEpochDay(standardFields.remove(EPOCH_DAY)));
412            return;
413        }
414
415        // normalize fields
416        if (standardFields.containsKey(EPOCH_MONTH)) {
417            long em = standardFields.remove(EPOCH_MONTH);
418            addFieldValue(MONTH_OF_YEAR, (em % 12) + 1);
419            addFieldValue(YEAR, (em / 12) + 1970);
420        }
421
422        // build date
423        if (standardFields.containsKey(YEAR)) {
424            if (standardFields.containsKey(MONTH_OF_YEAR)) {
425                if (standardFields.containsKey(DAY_OF_MONTH)) {
426                    int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR));
427                    int moy = Jdk8Methods.safeToInt(standardFields.remove(MONTH_OF_YEAR));
428                    int dom = Jdk8Methods.safeToInt(standardFields.remove(DAY_OF_MONTH));
429                    checkDate(LocalDate.of(y, moy, dom));
430                    return;
431                }
432                if (standardFields.containsKey(ALIGNED_WEEK_OF_MONTH)) {
433                    if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) {
434                        int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR));
435                        int moy = Jdk8Methods.safeToInt(standardFields.remove(MONTH_OF_YEAR));
436                        int aw = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_WEEK_OF_MONTH));
437                        int ad = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH));
438                        checkDate(LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7 + (ad - 1)));
439                        return;
440                    }
441                    if (standardFields.containsKey(DAY_OF_WEEK)) {
442                        int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR));
443                        int moy = Jdk8Methods.safeToInt(standardFields.remove(MONTH_OF_YEAR));
444                        int aw = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_WEEK_OF_MONTH));
445                        int dow = Jdk8Methods.safeToInt(standardFields.remove(DAY_OF_WEEK));
446                        checkDate(LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow))));
447                        return;
448                    }
449                }
450            }
451            if (standardFields.containsKey(DAY_OF_YEAR)) {
452                int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR));
453                int doy = Jdk8Methods.safeToInt(standardFields.remove(DAY_OF_YEAR));
454                checkDate(LocalDate.ofYearDay(y, doy));
455                return;
456            }
457            if (standardFields.containsKey(ALIGNED_WEEK_OF_YEAR)) {
458                if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) {
459                    int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR));
460                    int aw = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_WEEK_OF_YEAR));
461                    int ad = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR));
462                    checkDate(LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7 + (ad - 1)));
463                    return;
464                }
465                if (standardFields.containsKey(DAY_OF_WEEK)) {
466                    int y = Jdk8Methods.safeToInt(standardFields.remove(YEAR));
467                    int aw = Jdk8Methods.safeToInt(standardFields.remove(ALIGNED_WEEK_OF_YEAR));
468                    int dow = Jdk8Methods.safeToInt(standardFields.remove(DAY_OF_WEEK));
469                    checkDate(LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow))));
470                    return;
471                }
472            }
473        }
474    }
475
476    private void checkDate(LocalDate date) {
477        // TODO: this doesn't handle aligned weeks over into next month which would otherwise be valid
478
479        addCalendrical(date);
480        for (ChronoField field : standardFields.keySet()) {
481            long val1;
482            try {
483                val1 = date.getLong(field);
484            } catch (DateTimeException ex) {
485                continue;
486            }
487            Long val2 = standardFields.get(field);
488            if (val1 != val2) {
489                throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + date);
490            }
491        }
492    }
493
494    private void mergeTime() {
495        if (standardFields.containsKey(CLOCK_HOUR_OF_DAY)) {
496            long ch = standardFields.remove(CLOCK_HOUR_OF_DAY);
497            addFieldValue(HOUR_OF_DAY, ch == 24 ? 0 : ch);
498        }
499        if (standardFields.containsKey(CLOCK_HOUR_OF_AMPM)) {
500            long ch = standardFields.remove(CLOCK_HOUR_OF_AMPM);
501            addFieldValue(HOUR_OF_AMPM, ch == 12 ? 0 : ch);
502        }
503        if (standardFields.containsKey(AMPM_OF_DAY) && standardFields.containsKey(HOUR_OF_AMPM)) {
504            long ap = standardFields.remove(AMPM_OF_DAY);
505            long hap = standardFields.remove(HOUR_OF_AMPM);
506            addFieldValue(HOUR_OF_DAY, ap * 12 + hap);
507        }
508//        if (timeFields.containsKey(HOUR_OF_DAY) && timeFields.containsKey(MINUTE_OF_HOUR)) {
509//            long hod = timeFields.remove(HOUR_OF_DAY);
510//            long moh = timeFields.remove(MINUTE_OF_HOUR);
511//            addFieldValue(MINUTE_OF_DAY, hod * 60 + moh);
512//        }
513//        if (timeFields.containsKey(MINUTE_OF_DAY) && timeFields.containsKey(SECOND_OF_MINUTE)) {
514//            long mod = timeFields.remove(MINUTE_OF_DAY);
515//            long som = timeFields.remove(SECOND_OF_MINUTE);
516//            addFieldValue(SECOND_OF_DAY, mod * 60 + som);
517//        }
518        if (standardFields.containsKey(NANO_OF_DAY)) {
519            long nod = standardFields.remove(NANO_OF_DAY);
520            addFieldValue(SECOND_OF_DAY, nod / 1000_000_000L);
521            addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L);
522        }
523        if (standardFields.containsKey(MICRO_OF_DAY)) {
524            long cod = standardFields.remove(MICRO_OF_DAY);
525            addFieldValue(SECOND_OF_DAY, cod / 1000_000L);
526            addFieldValue(MICRO_OF_SECOND, cod % 1000_000L);
527        }
528        if (standardFields.containsKey(MILLI_OF_DAY)) {
529            long lod = standardFields.remove(MILLI_OF_DAY);
530            addFieldValue(SECOND_OF_DAY, lod / 1000);
531            addFieldValue(MILLI_OF_SECOND, lod % 1000);
532        }
533        if (standardFields.containsKey(SECOND_OF_DAY)) {
534            long sod = standardFields.remove(SECOND_OF_DAY);
535            addFieldValue(HOUR_OF_DAY, sod / 3600);
536            addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60);
537            addFieldValue(SECOND_OF_MINUTE, sod % 60);
538        }
539        if (standardFields.containsKey(MINUTE_OF_DAY)) {
540            long mod = standardFields.remove(MINUTE_OF_DAY);
541            addFieldValue(HOUR_OF_DAY, mod / 60);
542            addFieldValue(MINUTE_OF_HOUR, mod % 60);
543        }
544
545//            long sod = nod / 1000_000_000L;
546//            addFieldValue(HOUR_OF_DAY, sod / 3600);
547//            addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60);
548//            addFieldValue(SECOND_OF_MINUTE, sod % 60);
549//            addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L);
550        if (standardFields.containsKey(MILLI_OF_SECOND) && standardFields.containsKey(MICRO_OF_SECOND)) {
551            long los = standardFields.remove(MILLI_OF_SECOND);
552            long cos = standardFields.get(MICRO_OF_SECOND);
553            addFieldValue(MICRO_OF_SECOND, los * 1000 + (cos % 1000));
554        }
555
556        Long hod = standardFields.get(HOUR_OF_DAY);
557        Long moh = standardFields.get(MINUTE_OF_HOUR);
558        Long som = standardFields.get(SECOND_OF_MINUTE);
559        Long nos = standardFields.get(NANO_OF_SECOND);
560        if (hod != null) {
561            int hodVal = Jdk8Methods.safeToInt(hod);
562            if (moh != null) {
563                int mohVal = Jdk8Methods.safeToInt(moh);
564                if (som != null) {
565                    int somVal = Jdk8Methods.safeToInt(som);
566                    if (nos != null) {
567                        int nosVal = Jdk8Methods.safeToInt(nos);
568                        addCalendrical(LocalTime.of(hodVal, mohVal, somVal, nosVal));
569                    } else {
570                        addCalendrical(LocalTime.of(hodVal, mohVal, somVal));
571                    }
572                } else {
573                    addCalendrical(LocalTime.of(hodVal, mohVal));
574                }
575            } else {
576                addCalendrical(LocalTime.of(hodVal, 0));
577            }
578        }
579    }
580
581    private void splitObjects() {
582        List<Object> objectsToAdd = new ArrayList<>();
583        for (Object object : objects) {
584            if (object instanceof LocalDate || object instanceof LocalTime ||
585                            object instanceof ZoneId || object instanceof Chrono) {
586                continue;
587            }
588            if (object instanceof ZoneOffset || object instanceof Instant) {
589                objectsToAdd.add(object);
590
591            } else if (object instanceof TemporalAccessor) {
592                // TODO
593//                DateTimeAccessor dt = (DateTimeAccessor) object;
594//                objectsToAdd.add(dt.extract(LocalDate.class));
595//                objectsToAdd.add(dt.extract(LocalTime.class));
596//                objectsToAdd.add(dt.extract(ZoneId.class));
597//                objectsToAdd.add(dt.extract(Chrono.class));
598            }
599        }
600        for (Object object : objectsToAdd) {
601            if (object != null) {
602                addCalendrical(object);
603            }
604        }
605    }
606
607    //-----------------------------------------------------------------------
608    @Override
609    public <R> R query(TemporalQuery<R> query) {
610        if (query == TemporalQueries.zoneId()) {
611            R zone = extract(ZoneId.class);
612            if (zone == null) {
613                zone = extract(ZoneOffset.class);
614                if (zone == null && standardFields.containsKey(OFFSET_SECONDS)) {
615                    zone = (R) ZoneOffset.from(this);
616                }
617            }
618            return zone;
619        }
620        if (query == TemporalQueries.chrono()) {
621            return extract(Chrono.class);
622        }
623        // incomplete, so no need to handle TIME_PRECISION
624        return super.query(query);
625    }
626
627    @SuppressWarnings("unchecked")
628    public <R> R extract(Class<?> type) {
629        R result = null;
630        for (Object obj : objects) {
631            if (type.isInstance(obj)) {
632                if (result != null && result.equals(obj) == false) {
633                    throw new DateTimeException("Conflict found: " + type.getSimpleName() + " differs " + result + " vs " + obj + ": " + this);
634                }
635                result = (R) obj;
636            }
637        }
638        return result;
639    }
640
641    //-----------------------------------------------------------------------
642    /**
643     * Builds the specified type from the values in this builder.
644     * <p>
645     * This attempts to build the specified type from this builder.
646     * If the builder cannot return the type, an exception is thrown.
647     *
648     * @param <R>  the type to return
649     * @param type  the type to invoke {@code from} on, not null
650     * @return the extracted value, not null
651     * @throws DateTimeException if an error occurs
652     */
653    public <R> R build(Class<R> type) {
654        return invokeFrom(type, this);
655    }
656
657    /**
658     * Invokes the {@code from(DateTime)} method of a class.
659     * <p>
660     * This calls the {@code from} method with the specified date-time object.
661     * The from method will extract an object of the specified type if it can,
662     *
663     * @param <R>  the type to return
664     * @param type  the type to invoke {@code from} on, not null
665     * @param temporal  the date-time to pass as the argument, not null
666     * @return the value returned from the {@code from} method, not null
667     * @throws DateTimeException if an error occurs
668     */
669    private static <R> R invokeFrom(Class<R> type, TemporalAccessor temporal) {
670        try {
671            Method m = type.getDeclaredMethod("from", TemporalAccessor.class);
672            return type.cast(m.invoke(null, temporal));
673        } catch (ReflectiveOperationException ex) {
674            if (ex.getCause() instanceof DateTimeException == false) {
675                throw new DateTimeException("Unable to invoke method from(DateTime)", ex);
676            }
677            throw (DateTimeException) ex.getCause();
678        }
679    }
680
681    //-----------------------------------------------------------------------
682    /**
683     * Clones this builder, creating a new independent copy referring to the
684     * same map of fields and objects.
685     *
686     * @return the cloned builder, not null
687     */
688    @Override
689    public DateTimeBuilder clone() {
690        DateTimeBuilder dtb = new DateTimeBuilder();
691        dtb.objects.addAll(this.objects);
692        dtb.standardFields.putAll(this.standardFields);
693        dtb.standardFields.putAll(this.standardFields);
694        if (this.otherFields != null) {
695            dtb.otherFields.putAll(this.otherFields);
696        }
697        return dtb;
698    }
699
700    //-----------------------------------------------------------------------
701    @Override
702    public String toString() {
703        StringBuilder buf = new StringBuilder(128);
704        buf.append("DateTimeBuilder[");
705        Map<TemporalField, Long> fields = getFieldValueMap();
706        if (fields.size() > 0) {
707            buf.append("fields=").append(fields);
708        }
709        if (objects.size() > 0) {
710            if (fields.size() > 0) {
711                buf.append(", ");
712            }
713            buf.append("objects=").append(objects);
714        }
715        buf.append(']');
716        return buf.toString();
717    }
718
719    //-----------------------------------------------------------------------
720    @Override
721    public boolean isSupported(TemporalField field) {
722        return field != null && containsFieldValue(field);
723    }
724
725    @Override
726    public long getLong(TemporalField field) {
727        return getFieldValue(field);
728    }
729
730}